blob: 7c3a95ca0a8388efeec08e2225ddb4370b97c7d6 [file] [log] [blame]
// Copyright (c) 2016, 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 dx.DexMergerTask
import dx.DxTask
import desugaredlibrary.CustomConversionAsmRewriterTask
import net.ltgt.gradle.errorprone.CheckSeverity
import org.gradle.internal.os.OperatingSystem
import smali.SmaliTask
import tasks.DownloadDependency
import tasks.GetJarsFromConfiguration
import utils.Utils
buildscript {
repositories {
maven {
url 'https://storage.googleapis.com/r8-deps/maven_mirror/'
}
mavenCentral()
gradlePluginPortal()
jcenter()
}
}
plugins {
id "net.ltgt.errorprone" version "0.7"
}
apply plugin: 'java'
apply plugin: 'idea'
ext {
androidSupportVersion = '25.4.0'
asmVersion = '9.3' // When updating update tools/asmifier.py, build.src and Toolhelper as well.
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '30.1.1-jre'
joptSimpleVersion = '4.6'
gsonVersion = '2.7'
junitVersion = '4.13-beta-2'
mockitoVersion = '2.10.0'
// The kotlin version is only here to specify the kotlin language level,
// all kotlin compilations are done in tests.
kotlinVersion = '1.6.0'
kotlinExtMetadataJVMVersion = '0.4.1'
smaliVersion = '2.2b4'
errorproneVersion = '2.3.2'
testngVersion = '6.10'
}
repositories {
maven {
url 'https://storage.googleapis.com/r8-deps/maven_mirror/'
}
google()
mavenCentral()
}
if (project.hasProperty('with_code_coverage')) {
apply plugin: 'jacoco'
}
// Custom source set for example tests and generated tests.
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs "third_party/api_database/api_database"
}
}
main11 {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs "third_party/api_database/api_database"
}
}
test {
java {
srcDirs = [
'src/test/java',
'build/generated/test/java',
]
}
}
apiUsageSample {
java {
srcDirs = ['src/test/apiUsageSample', 'src/main/java']
include 'com/android/tools/apiusagesample/*.java'
include 'com/android/tools/r8/BaseCompilerCommandParser.java'
include 'com/android/tools/r8/D8CommandParser.java'
include 'com/android/tools/r8/R8CommandParser.java'
include 'com/android/tools/r8/utils/FlagFile.java'
}
}
cfSegments {
java {
srcDirs = ['third_party/classlib/java', 'src/cf_segments/java']
}
output.resourcesDir = 'build/classes/cfSegments'
}
libraryDesugarConversions {
java {
srcDirs = ['src/library_desugar/java']
}
output.resourcesDir = 'build/classes/library_desugar_conversions'
}
debugTestResources {
java {
srcDirs = ['src/test/debugTestResources']
}
output.resourcesDir = 'build/classes/debugTestResources'
}
debugTestResourcesJava8 {
java {
srcDirs = ['src/test/debugTestResourcesJava8']
}
output.resourcesDir = 'build/classes/debugTestResourcesJava8'
}
examples {
java {
srcDirs = ['src/test/examples']
}
output.resourcesDir = 'build/classes/examples'
}
examplesJava9 {
java {
srcDirs = ['src/test/examplesJava9']
}
}
examplesJava10 {
java {
srcDirs = ['src/test/examplesJava10']
}
}
examplesJava11 {
java {
srcDirs = ['src/test/examplesJava11']
}
}
examplesJava17 {
java {
srcDirs = ['src/test/examplesJava17']
}
}
examplesJava18 {
java {
srcDirs = ['src/test/examplesJava18']
}
}
examplesTestNGRunner {
java {
srcDirs = ['src/test/testngrunner']
}
}
examplesAndroidN {
java {
srcDirs = ['src/test/examplesAndroidN']
}
output.resourcesDir = 'build/classes/examplesAndroidN'
}
examplesAndroidO {
java {
srcDirs = ['src/test/examplesAndroidO']
}
output.resourcesDir = 'build/classes/examplesAndroidO'
}
examplesAndroidP {
java {
srcDirs = ['src/test/examplesAndroidP']
}
output.resourcesDir = 'build/classes/examplesAndroidP'
}
examplesProto {
java {
srcDirs = ['src/test/examplesProto']
}
compileClasspath = files("third_party/protobuf-lite/libprotobuf_lite.jar")
compileClasspath += fileTree(dir: "build/generated/test/proto", include: "*.jar")
output.resourcesDir = 'build/classes/examplesProto'
}
kotlinR8TestResources {
java {
srcDirs = ['src/test/kotlinR8TestResources']
}
output.resourcesDir = 'build/classes/kotlinR8TestResources'
}
}
// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
// use the output path for tests (ultimately through ToolHelper.getClassPathForTests()) and
// therefore these paths need to be the same. See https://youtrack.jetbrains.com/issue/IDEA-175172
// for context.
idea {
sourceSets.all { SourceSet sources ->
module {
if (sources.name == "main") {
sourceDirs += sources.java.srcDirs
outputDir sources.output.classesDirs[0]
} else {
testSourceDirs += sources.java.srcDirs
testOutputDir sources.output.classesDirs[0]
}
}
}
}
configurations {
supportLibs
}
dependencies {
implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
implementation "com.google.code.gson:gson:$gsonVersion"
// Include all of guava when compiling the code, but exclude annotations that we don't
// need from the packaging.
compileOnly("com.google.guava:guava:$guavaVersion")
implementation("com.google.guava:guava:$guavaVersion", {
exclude group: 'com.google.errorprone'
exclude group: 'com.google.code.findbugs'
exclude group: 'com.google.j2objc'
exclude group: 'org.codehaus.mojo'
})
implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
main11Implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
main11Implementation "com.google.code.gson:gson:$gsonVersion"
// Include all of guava when compiling the code, but exclude annotations that we don't
// need from the packaging.
main11CompileOnly("com.google.guava:guava:$guavaVersion")
main11Implementation("com.google.guava:guava:$guavaVersion", {
exclude group: 'com.google.errorprone'
exclude group: 'com.google.code.findbugs'
exclude group: 'com.google.j2objc'
exclude group: 'org.codehaus.mojo'
})
main11Implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
main11Implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
main11Implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
main11Implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
main11Implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
main11Implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
main11Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
testCompile sourceSets.examples.output
testCompile "junit:junit:$junitVersion"
testCompile "com.google.guava:guava:$guavaVersion"
testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
testCompile group: 'org.smali', name: 'smali', version: smaliVersion
testCompile files('third_party/jasmin/jasmin-2.4.jar')
testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
testCompile files('third_party/ddmlib/ddmlib.jar')
testCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
testCompile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
testCompile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
testCompile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
testCompile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
testCompile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
// Import Guava for @Nullable annotation
examplesCompile "com.google.guava:guava:$guavaVersion"
examplesCompile "junit:junit:$junitVersion"
examplesCompile "org.mockito:mockito-core:$mockitoVersion"
supportLibs "com.android.support:support-v4:$androidSupportVersion"
supportLibs "junit:junit:$junitVersion"
supportLibs "com.android.support.test.espresso:espresso-core:$espressoVersion"
apiUsageSampleCompile sourceSets.main.output
apiUsageSampleCompile "com.google.guava:guava:$guavaVersion"
kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
}
def r8LibPath = "$buildDir/libs/r8lib.jar"
def r8LibExludeDepsPath = "$buildDir/libs/r8lib-exclude-deps.jar"
def r8DesugaredPath = "$buildDir/libs/r8desugared.jar"
def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
def r8LibTestPath = "$buildDir/classes/r8libtest"
def java11ClassFiles = "$buildDir/classes/java/mainJava11"
def r8RetracePath = "$buildDir/libs/r8retrace.jar"
def r8RetraceExludeDepsPath = "$buildDir/libs/r8retrace-exclude-deps.jar"
def osString = OperatingSystem.current().isLinux() ? "linux" :
OperatingSystem.current().isMacOsX() ? "mac" : "windows"
def cloudDependencies = [
"tests" : [
"2017-10-04/art",
"2016-12-19/art"
],
"third_party": [
"android_cts_baseline",
"android_jar/libcore_latest",
"android_jar/lib-v14",
"android_jar/lib-v15",
"android_jar/lib-v19",
"android_jar/lib-v21",
"android_jar/lib-v22",
"android_jar/lib-v23",
"android_jar/lib-v24",
"android_jar/lib-v25",
"android_jar/lib-v26",
"android_jar/lib-v27",
"android_jar/lib-v28",
"android_jar/lib-v29",
"android_jar/lib-v30",
"android_jar/lib-v31",
"android_jar/lib-v32",
"android_jar/lib-v33",
"api_database/api_database",
"api-outlining/simple-app-dump",
"binary_compatibility_tests/compiler_api_tests",
"core-lambda-stubs",
"dagger/2.41",
"dart-sdk",
"ddmlib",
"gradle/gradle",
"google/google-java-format/1.14.0",
"google-java-format",
"iosched_2019",
"jacoco/0.8.2",
"jacoco/0.8.6",
"jasmin",
"junit",
"jdwp-tests",
"jsr223-api-1.0",
"rhino-1.7.10",
"rhino-android-1.1.1",
"kotlin/kotlin-compiler-1.3.11",
"kotlin/kotlin-compiler-1.3.41",
"kotlin/kotlin-compiler-1.3.72",
"kotlin/kotlin-compiler-1.4.20",
"kotlin/kotlin-compiler-1.5.0",
"kotlin/kotlin-compiler-1.6.0",
"kotlinx-coroutines-1.3.6",
"openjdk/openjdk-rt-1.8",
"openjdk/desugar_jdk_libs",
"openjdk/desugar_jdk_libs_11",
"openjdk/desugar_jdk_libs_legacy",
"openjdk/desugar_jdk_libs_releases/1.0.9",
"openjdk/desugar_jdk_libs_releases/1.0.10",
"openjdk/desugar_jdk_libs_releases/1.1.0",
"openjdk/desugar_jdk_libs_releases/1.1.1",
"openjdk/desugar_jdk_libs_releases/1.1.5",
"openjdk/jdk-11-test",
"opensource-apps/tivi",
"proguard/proguard5.2.1",
"proguard/proguard6.0.1",
"proguard/proguard-7.0.0",
"retrace_benchmark",
"retrace/binary_compatibility",
"r8",
"r8-releases/2.0.74",
"r8-releases/3.2.54",
"r8mappings",
"tachiyomi"
],
// All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
// container on other platforms where supported.
"tools" : [
"linux/art",
"linux/art-5.1.1",
"linux/art-6.0.1",
"linux/art-7.0.0",
"linux/art-8.1.0",
"linux/art-9.0.0",
"linux/art-10.0.0",
"linux/host/art-12.0.0-beta4",
"linux/host/art-13-dev",
"linux/dalvik",
"linux/dalvik-4.0.4",
"${osString}/dx",
]
]
def cloudSystemDependencies = [
linux: [
"third_party": ["openjdk/openjdk-9.0.4/linux",
"openjdk/jdk8/linux-x86",
"openjdk/jdk-11/linux",
"openjdk/jdk-17/linux",
"openjdk/jdk-18/linux"],
],
osx: [
"third_party": ["openjdk/openjdk-9.0.4/osx",
"openjdk/jdk8/darwin-x86",
"openjdk/jdk-11/osx",
"openjdk/jdk-17/osx",
"openjdk/jdk-18/osx"],
],
windows: [
"third_party": ["openjdk/openjdk-9.0.4/windows",
"openjdk/jdk-11/windows",
"openjdk/jdk-17/windows",
"openjdk/jdk-18/windows"],
],
]
if (OperatingSystem.current().isWindows()) {
cloudSystemDependencies.windows.each { entry ->
cloudDependencies.get(entry.key).addAll(entry.value)
}
} else if (OperatingSystem.current().isLinux()) {
cloudSystemDependencies.linux.each { entry ->
cloudDependencies.get(entry.key).addAll(entry.value)
}
} else if (OperatingSystem.current().isMacOsX()) {
cloudSystemDependencies.osx.each { entry ->
cloudDependencies.get(entry.key).addAll(entry.value)
}
} else {
println "WARNING: Unsupported system: " + OperatingSystem.current()
}
def getDownloadDepsTaskName(entryKey, entryFile) {
return "download_deps_${entryKey}_${entryFile.replace('/', '_').replace('\\', '_')}"
}
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
task (getDownloadDepsTaskName(entry.key, entryFile), type: DownloadDependency) {
type DownloadDependency.Type.GOOGLE_STORAGE
dependency "${entry.key}/${entryFile}"
}
}
}
def x20Dependencies = [
"third_party": [
"benchmarks/kotlin-benches",
"chrome/chrome_180917_ffbaa8",
"chrome/chrome_200430",
"chrome/monochrome_public_minimal_apks/chrome_200520",
"chrome/clank_google3_prebuilt",
"classlib",
"cf_segments",
"desugar/desugar_20180308",
"internal/issue-127524985",
"framework",
"gmail/gmail_android_170604.16",
"gmail/gmail_android_180826.15",
"gmscore/gmscore_v10",
"gmscore/latest",
"nest/nest_20180926_7c6cfb",
"proguard/proguard_internal_159423826",
"proguardsettings",
"proto",
"protobuf-lite",
"retrace_internal",
"youtube/youtube.android_16.20",
"youtube/youtube.android_17.19"
],
]
x20Dependencies.each { entry ->
entry.value.each { entryFile ->
task "${getDownloadDepsTaskName(entry.key, entryFile)}"(type: DownloadDependency) {
type DownloadDependency.Type.X20
dependency "${entry.key}/${entryFile}"
}
}
}
task downloadProguard {
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
if (entryFile.contains("proguard")) {
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
}
task downloadOpenJDKrt {
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
if (entryFile.contains("openjdk-rt")) {
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
}
task downloadDx {
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
if (entryFile.endsWith("/dx")) {
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
}
task downloadAndroidCts {
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
if (entryFile.contains("android_cts_baseline")) {
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
}
task downloadCloudDeps() {
cloudDependencies.each { entry ->
entry.value.each { entryFile ->
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
task downloadX20Deps() {
x20Dependencies.each { entry ->
entry.value.each { entryFile ->
dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
}
}
}
task downloadDeps {
dependsOn downloadCloudDeps
if (!project.hasProperty('no_internal')) {
dependsOn downloadX20Deps
}
}
allprojects {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// TODO(ricow): Remove debug prints
println("NOTE: Current operating system: " + OperatingSystem.current())
println("NOTE: Current operating system isWindows: " + OperatingSystem.current().isWindows())
// Check if running with the JDK location from tools/jdk.py.
if (OperatingSystem.current().isWindows()) {
println "NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome
compileJava.options.encoding = "UTF-8"
compileTestJava.options.encoding = "UTF-8"
} else {
def javaHomeOut = new StringBuilder()
def javaHomeErr = new StringBuilder()
def javaHomeProc = './tools/jdk.py'.execute([], projectDir)
javaHomeProc.waitForProcessOutput(javaHomeOut, javaHomeErr)
def jdkHome = new File(javaHomeOut.toString().trim())
if (!jdkHome.exists()) {
println "WARNING: Failed to find the ./tools/jdk.py specified JDK: " + jdkHome
} else if (jdkHome != org.gradle.internal.jvm.Jvm.current().javaHome) {
println("WARNING: Gradle is running in a non-pinned Java"
+ ". Gradle Java Home: " + org.gradle.internal.jvm.Jvm.current().javaHome
+ ". Expected: " + jdkHome)
} else {
println("NOTE: Running with jdk from tools/jdk.py: " + jdkHome)
}
}
compileJava.dependsOn downloadCloudDeps
sourceSets.configureEach { sourceSet ->
tasks.named(sourceSet.compileJavaTaskName).configure {
// Default disable errorprone (enabled and setup below).
options.errorprone.enabled = false
options.compilerArgs << '-Xlint:unchecked'
// Run all compilation tasks in a forked subprocess.
options.fork = true
// Javac often runs out of stack space when compiling the tests.
// Increase the stack size for the javac process.
options.forkOptions.jvmArgs << "-Xss256m"
// Test compilation is sometimes hitting the default limit at 1g, increase it.
options.forkOptions.jvmArgs << "-Xmx3g"
// Set the bootclass path so compilation is consistent with 1.8 target compatibility.
options.forkOptions.jvmArgs << "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar"
}
}
def setJdkCompilationWithCompatibility(String sourceSet, String javaHome, JavaVersion compatibility, boolean enablePreview) {
tasks.named(sourceSet).get().configure {
def jdkDir = "third_party/openjdk/${javaHome}/"
options.fork = true
options.forkOptions.jvmArgs = []
if (enablePreview) {
options.compilerArgs.add('--enable-preview')
}
if (OperatingSystem.current().isLinux()) {
dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/linux")
options.forkOptions.javaHome = file(jdkDir + 'linux')
} else if (OperatingSystem.current().isMacOsX()) {
dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/osx")
options.forkOptions.javaHome = compatibility > JavaVersion.VERSION_1_9
? file(jdkDir + 'osx/Contents/Home')
: file(jdkDir + 'osx')
} else {
dependsOn getDownloadDepsTaskName("third_party", "openjdk/" + javaHome + "/windows")
options.forkOptions.javaHome = file(jdkDir + 'windows')
}
sourceCompatibility = compatibility
targetCompatibility = compatibility
}
}
setJdkCompilationWithCompatibility(
sourceSets.examplesJava9.compileJavaTaskName,
'openjdk-9.0.4',
JavaVersion.VERSION_1_9,
false)
setJdkCompilationWithCompatibility(
sourceSets.examplesJava10.compileJavaTaskName,
'jdk-11',
JavaVersion.VERSION_1_10,
false)
setJdkCompilationWithCompatibility(
sourceSets.main11.compileJavaTaskName,
'jdk-11',
JavaVersion.VERSION_11,
false)
setJdkCompilationWithCompatibility(
sourceSets.examplesJava11.compileJavaTaskName,
'jdk-11',
JavaVersion.VERSION_11,
false)
setJdkCompilationWithCompatibility(
sourceSets.examplesTestNGRunner.compileJavaTaskName,
'jdk-11',
JavaVersion.VERSION_11,
false)
setJdkCompilationWithCompatibility(
sourceSets.examplesJava17.compileJavaTaskName,
'jdk-17',
JavaVersion.VERSION_17,
false)
setJdkCompilationWithCompatibility(
sourceSets.examplesJava18.compileJavaTaskName,
'jdk-18',
// TODO(b/218293990): Update Gradle to get JavaVersion.VERSION_18.
JavaVersion.VERSION_17,
false)
task provideJdk11TestsDependencies(type: org.gradle.api.tasks.Copy) {
from sourceSets.examplesTestNGRunner.compileClasspath
include "**/**.jar"
into file("$buildDir/test/jdk11Tests")
}
task compileTestNGRunner (type: JavaCompile) {
dependsOn provideJdk11TestsDependencies
destinationDir = file("$buildDir/classes/java/examplesTestNGRunner")
source = sourceSets.examplesTestNGRunner.allSource
classpath = sourceSets.examplesTestNGRunner.compileClasspath
}
if (!project.hasProperty('without_error_prone') &&
// Don't enable error prone on Java 8 as the plugin setup does not support it.
!org.gradle.internal.jvm.Jvm.current().javaVersion.java8) {
compileJava {
// Enable error prone for D8/R8 sources.
options.errorprone.enabled = true
options.errorprone.disableAllChecks = true
options.errorprone.check('ClassCanBeStatic', CheckSeverity.ERROR)
options.errorprone.check('CollectionIncompatibleType', CheckSeverity.ERROR)
options.errorprone.check('OperatorPrecedence', CheckSeverity.ERROR)
options.errorprone.check('RemoveUnusedImports', CheckSeverity.ERROR)
options.errorprone.check('MissingOverride', CheckSeverity.ERROR)
options.errorprone.check('IntLongMath', CheckSeverity.ERROR)
options.errorprone.check('EqualsHashCode', CheckSeverity.ERROR)
options.errorprone.check('InconsistentOverloads', CheckSeverity.ERROR)
options.errorprone.check('ArrayHashCode', CheckSeverity.ERROR)
options.errorprone.check('EqualsIncompatibleType', CheckSeverity.ERROR)
options.errorprone.check('NonOverridingEquals', CheckSeverity.ERROR)
options.errorprone.check('FallThrough', CheckSeverity.ERROR)
options.errorprone.check('MissingCasesInEnumSwitch', CheckSeverity.ERROR)
options.errorprone.check('MissingDefault', CheckSeverity.ERROR)
options.errorprone.check('MultipleTopLevelClasses', CheckSeverity.ERROR)
options.errorprone.check('NarrowingCompoundAssignment', CheckSeverity.ERROR)
options.errorprone.check('BoxedPrimitiveConstructor', CheckSeverity.ERROR)
options.errorprone.check('LogicalAssignment', CheckSeverity.ERROR)
options.errorprone.check('FloatCast', CheckSeverity.ERROR)
options.errorprone.check('ReturnValueIgnored', CheckSeverity.ERROR)
}
}
task consolidatedLicense {
def license = new File(new File(buildDir, 'generatedLicense'), 'LICENSE')
inputs.files files('LICENSE', 'LIBRARY-LICENSE') + fileTree(dir: 'library-licensing')
def runtimeClasspath = configurations.findByName("runtimeClasspath")
inputs.files { runtimeClasspath.getResolvedConfiguration().files }
outputs.files license
doLast {
def dependencies = []
runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each {
def identifier = (ModuleComponentIdentifier) it.id.componentIdentifier
dependencies.add("${identifier.group}:${identifier.module}")
}
def libraryLicenses = file('LIBRARY-LICENSE').text
dependencies.each {
if (!libraryLicenses.contains("- artifact: $it")) {
throw new GradleException("No license for $it in LIBRARY_LICENSE")
}
}
license.getParentFile().mkdirs()
license.createNewFile()
license.text = "This file lists all licenses for code distributed.\n"
license.text += "All non-library code has the following 3-Clause BSD license.\n"
license.text += "\n"
license.text += "\n"
license.text += file('LICENSE').text
license.text += "\n"
license.text += "\n"
license.text += "Summary of distributed libraries:\n"
license.text += "\n"
license.text += libraryLicenses
license.text += "\n"
license.text += "\n"
license.text += "Licenses details:\n"
fileTree(dir: 'library-licensing').getFiles().stream().sorted().forEach { file ->
license.text += "\n"
license.text += "\n"
license.text += file.text
}
}
}
def repackageDepFile(file) {
if (file.getName().endsWith('.jar')) {
return zipTree(file).matching {
exclude '**/module-info.class'
exclude 'META-INF/maven/**'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/MANIFEST.MF'
}
} else {
return fileTree(file)
}
}
task repackageDeps(type: Jar) {
dependsOn downloadCloudDeps
dependsOn project.configurations.runtimeClasspath
project.configurations.runtimeClasspath.forEach {
from repackageDepFile(it)
}
archiveFileName = 'deps_all.jar'
}
task repackageTestDeps(type: Jar) {
dependsOn downloadCloudDeps
dependsOn project.configurations.testCompile
project.configurations.testCompile.forEach {
from repackageDepFile(it)
}
archiveFileName = 'test_deps_all.jar'
}
task repackageSources(type: Jar) {
// If this fails then remove all generated folders from
// build/classes/java/test that is not {com,dalvik}
from sourceSets.main.output
archiveFileName = 'sources_main.jar'
}
task repackageSources11(type: Jar) {
from sourceSets.main11.output
archiveFileName = 'sources_main_11.jar'
}
def r8CreateTask(name, baseName, sources, includeLibraryLicenses, includeSwissArmyKnife) {
return tasks.create("r8Create${name}", Jar) {
entryCompression ZipEntryCompression.STORED
dependsOn sources
dependsOn files('LICENSE')
if (includeLibraryLicenses) {
from consolidatedLicense.outputs.files
} else {
from files('LICENSE')
}
from sources.collect { zipTree(it) }
exclude "$buildDir/classes/**"
archiveFileName = baseName
if (includeSwissArmyKnife) {
manifest {
attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
}
}
exclude "META-INF/*.kotlin_module"
exclude "**/*.kotlin_metadata"
}
}
def r8RelocateTask(r8Task, output) {
return tasks.create("r8Relocate_${r8Task.name}", Exec) {
dependsOn r8WithDeps
dependsOn r8Task
outputs.file output
workingDir = projectDir
inputs.files r8Task.outputs.files + r8WithDeps.outputs.files
commandLine baseCompilerCommandLine([
"relocator",
"--input",
r8Task.outputs.files[0],
"--output",
output,
"--map",
"com.google.common->com.android.tools.r8.com.google.common",
"--map",
"com.google.gson->com.android.tools.r8.com.google.gson",
"--map",
"com.google.thirdparty->com.android.tools.r8.com.google.thirdparty",
"--map",
"joptsimple->com.android.tools.r8.joptsimple",
"--map",
"org.objectweb.asm->com.android.tools.r8.org.objectweb.asm",
"--map",
"it.unimi.dsi.fastutil->com.android.tools.r8.it.unimi.dsi.fastutil",
"--map",
"kotlin->com.android.tools.r8.jetbrains.kotlin",
"--map",
"kotlinx->com.android.tools.r8.jetbrains.kotlinx",
"--map",
"org.jetbrains->com.android.tools.r8.org.jetbrains",
"--map",
"org.intellij->com.android.tools.r8.org.intellij",
"--map",
"org.checkerframework->com.android.tools.r8.org.checkerframework"
])
}
}
task r8WithDeps {
dependsOn repackageSources
dependsOn repackageDeps
inputs.files ([repackageSources.outputs, repackageDeps.outputs])
def r8Task = r8CreateTask(
'WithDeps',
'r8_with_deps.jar',
repackageSources.outputs.files + repackageDeps.outputs.files,
true,
true)
dependsOn r8Task
outputs.files r8Task.outputs.files
}
task r8WithDeps11 {
dependsOn repackageSources11
dependsOn repackageDeps
inputs.files ([repackageSources11.outputs, repackageDeps.outputs])
def r8Task = r8CreateTask(
'WithDeps11',
'r8_with_deps_11.jar',
repackageSources11.outputs.files + repackageDeps.outputs.files,
true,
true)
dependsOn r8Task
outputs.files r8Task.outputs.files
}
task r8WithRelocatedDeps {
def output = "${buildDir}/libs/r8_with_relocated_deps.jar"
dependsOn r8RelocateTask(r8WithDeps, output)
inputs.files r8WithDeps.outputs.files
outputs.file output
}
task r8WithRelocatedDeps11 {
def output = "${buildDir}/libs/r8_with_relocated_deps_11.jar"
dependsOn r8RelocateTask(r8WithDeps11, output)
inputs.files r8WithDeps11.outputs.files
outputs.file output
}
task r8WithoutDeps {
dependsOn repackageSources
inputs.files repackageSources.outputs
def r8Task = r8CreateTask(
'WithoutDeps',
'r8_without_deps.jar',
repackageSources.outputs.files,
false,
true)
dependsOn r8Task
outputs.files r8Task.outputs.files
}
task r8(type: Copy) {
def r8Task = project.hasProperty("exclude_deps")
? r8WithoutDeps : r8WithRelocatedDeps
dependsOn r8Task
from r8Task.outputs.files[0]
into file("${buildDir}/libs")
rename { String fileName -> "r8.jar" }
outputs.file "${buildDir}/libs/r8.jar"
}
task r8NoManifestWithoutDeps {
dependsOn repackageSources
inputs.files repackageSources.outputs
def r8Task = r8CreateTask(
'NoManifestWithoutDeps',
'r8_no_manifest_without_deps.jar',
repackageSources.outputs.files,
false,
false)
dependsOn r8Task
outputs.files r8Task.outputs.files
}
task r8NoManifestWithDeps {
dependsOn repackageSources
inputs.files ([repackageSources.outputs, repackageDeps.outputs])
def r8Task = r8CreateTask(
'NoManifestWithDeps',
'r8_no_manifest_with_deps.jar',
repackageSources.outputs.files + repackageDeps.outputs.files,
true,
false)
dependsOn r8Task
outputs.files r8Task.outputs.files
}
task r8NoManifestWithRelocatedDeps {
def output = "${buildDir}/libs/r8_no_manifest_with_relocated_deps.jar"
dependsOn r8RelocateTask(r8NoManifestWithDeps, output)
inputs.files r8NoManifestWithDeps.outputs.files
outputs.file output
}
task r8NoManifest(type: Copy) {
def r8Task = project.hasProperty("exclude_deps")
? r8NoManifestWithoutDeps : r8NoManifestWithRelocatedDeps
dependsOn r8Task
from r8Task.outputs.files[0]
into file("${buildDir}/libs")
rename { String fileName -> "r8_no_manifest.jar" }
outputs.file "${buildDir}/libs/r8_no_manifest.jar"
}
def baseCompilerCommandLine(compiler, args = []) {
// Execute r8 commands against a stable r8 with dependencies.
// TODO(b/139725780): See if we can remove or lower the heap size (-Xmx8g).
return [org.gradle.internal.jvm.Jvm.current().getJavaExecutable(),
"-Xmx8g", "-ea", "-jar", r8WithDeps.outputs.files[0]] + compiler + args
}
def baseR8CommandLine(args = []) {
// Execute r8 commands against a stable r8 with dependencies.
return baseCompilerCommandLine("r8", args)
}
def baseD8CommandLine(args = []) {
// Execute r8 commands against a stable r8 with dependencies.
return baseCompilerCommandLine("d8", args)
}
def r8CfCommandLine(input, output, pgConfs = [], args = ["--release"], libs = []) {
def allArgs = [
"--classfile",
input,
"--output", output,
"--pg-map-output", output + ".map",
"--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
] + args + libs.collectMany { ["--lib", it] } + pgConfs.collectMany { ["--pg-conf", it] }
return baseR8CommandLine(allArgs)
}
def d8CfCommandLine(input, output, args = ["--release"], libs = []) {
def allArgs = [
"--classfile",
input,
"--output", output,
"--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
] + args + libs.collectMany { ["--lib", it] }
return baseD8CommandLine(allArgs)
}
def r8LibCreateTask(name, pgConfs = [], r8Task, output, libs = [], classpath = [], excldeps=false) {
return tasks.create("r8Lib${name}", Exec) {
inputs.files ([pgConfs, r8WithRelocatedDeps.outputs, r8Task.outputs])
outputs.file output
dependsOn downloadOpenJDKrt
dependsOn r8WithRelocatedDeps
dependsOn r8Task
commandLine ([
"python3", "tools/create_r8lib.py",
"--r8jar", r8Task.outputs.files[0],
"--output", output]
+ (excldeps ? ['--excldeps-variant'] : [])
+ (pgConfs.collectMany { ["--pg-conf", it] })
+ (libs.collectMany { ["--lib", it] })
+ (classpath.collectMany { ["--classpath", it] }))
workingDir = projectDir
}
}
task rawBuildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
from sourceSets.libraryDesugarConversions.output
include "java/**/*.class"
baseName 'library_desugar_conversions_raw'
destinationDir file('build/tmp/desugaredlibrary')
}
task buildLibraryDesugarConversions(type: CustomConversionAsmRewriterTask, dependsOn: rawBuildLibraryDesugarConversions) {
rawJar = file("build/tmp/desugaredlibrary/library_desugar_conversions_raw.zip")
outputDirectory = file("build/libs")
}
task testJarSources(type: Jar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
archiveFileName = "r8testsbase.jar"
from sourceSets.test.output
// We only want to include tests that use R8 when generating keep rules for applymapping.
include "com/android/tools/r8/**"
include "dalvik/**"
}
task testJar(type: Exec) {
dependsOn r8WithDeps
dependsOn testJarSources
def output = "$buildDir/libs/r8tests.jar"
outputs.file output
workingDir = projectDir
inputs.files (testJarSources.outputs.files + r8WithDeps.outputs.files)
commandLine baseCompilerCommandLine([
"relocator",
"--input",
testJarSources.outputs.files[0],
"--output",
output,
"--map",
"kotlinx.metadata->com.android.tools.r8.jetbrains.kotlinx.metadata"
])
}
task generateR8LibKeepRules(type: Exec) {
// Depend on r8WithRelocatedDeps to ensure that we do not have external
// dependencies crossing the boundary.
dependsOn r8WithDeps
dependsOn r8NoManifestWithRelocatedDeps
dependsOn testJar
dependsOn repackageTestDeps
dependsOn downloadOpenJDKrt
inputs.files ([
r8WithDeps.outputs,
r8NoManifestWithRelocatedDeps.outputs,
repackageDeps.outputs,
repackageTestDeps.outputs,
testJar.outputs])
outputs.file r8LibGeneratedKeepRulesPath
commandLine baseCompilerCommandLine([
"tracereferences",
"--keep-rules",
"--allowobfuscation",
"--lib",
"third_party/openjdk/openjdk-rt-1.8/rt.jar",
"--lib",
repackageDeps.outputs.files[0],
"--lib",
repackageTestDeps.outputs.files[0],
"--target",
r8NoManifestWithRelocatedDeps.outputs.files[0],
"--source",
testJar.outputs.files[0],
"--output",
r8LibGeneratedKeepRulesPath])
workingDir = projectDir
}
task R8Lib {
dependsOn r8LibCreateTask(
"Main",
["src/main/keep.txt", generateR8LibKeepRules.outputs.files[0]],
r8NoManifestWithRelocatedDeps,
r8LibPath,
).dependsOn(generateR8LibKeepRules)
inputs.files r8NoManifestWithRelocatedDeps.outputs.files
outputs.file r8LibPath
}
task R8LibNoDeps {
dependsOn r8LibCreateTask(
"MainNoDeps",
["src/main/keep.txt"],
r8NoManifestWithoutDeps,
r8LibExludeDepsPath,
[],
repackageDeps.outputs.files,
true,
).dependsOn(repackageDeps)
inputs.files ([r8NoManifestWithoutDeps.outputs, repackageDeps.outputs])
outputs.file r8LibExludeDepsPath
}
task R8Desugared(type: Exec) {
dependsOn downloadOpenJDKrt
dependsOn r8NoManifestWithRelocatedDeps
inputs.files r8NoManifestWithRelocatedDeps.outputs.files
commandLine d8CfCommandLine(
r8NoManifestWithRelocatedDeps.outputs.files[0],
r8DesugaredPath,
["--release"])
workingDir = projectDir
outputs.file r8DesugaredPath
}
task R8Retrace {
dependsOn R8Lib
dependsOn r8LibCreateTask(
"Retrace",
["src/main/keep_retrace.txt"],
R8Lib,
r8RetracePath,
).dependsOn(R8Lib)
outputs.file r8RetracePath
}
task R8RetraceNoDeps {
dependsOn R8LibNoDeps
dependsOn r8LibCreateTask(
"RetraceNoDeps",
["src/main/keep_retrace.txt"],
R8LibNoDeps,
r8RetraceExludeDepsPath,
[],
repackageDeps.outputs.files,
true,
).dependsOn(R8LibNoDeps)
outputs.file r8RetraceExludeDepsPath
}
task sourceJar(type: Jar, dependsOn: classes) {
classifier = 'src'
from sourceSets.main.allSource
}
artifacts {
archives sourceJar
}
task createArtTests(type: Exec) {
def outputDir = "build/generated/test/java/com/android/tools/r8/art"
def createArtTestsScript = "tools/create_art_tests.py"
inputs.files files("tests/2017-10-04/art.tar.gz", createArtTestsScript)
outputs.dir outputDir
dependsOn downloadDeps
commandLine "python3", createArtTestsScript
workingDir = projectDir
}
compileTestJava {
dependsOn createArtTests
}
task buildCfSegments(type: Jar, dependsOn: downloadDeps) {
from sourceSets.cfSegments.output
archiveFileName = 'cf_segments.jar'
destinationDir file('build/libs')
}
task buildR8ApiUsageSample(type: Jar) {
from sourceSets.apiUsageSample.output
archiveFileName = 'r8_api_usage_sample.jar'
destinationDir file('tests')
}
task buildApiSampleJars {
dependsOn buildR8ApiUsageSample
}
task buildDebugTestResourcesJars {
def resourcesDir = file("src/test/debugTestResources")
def hostJar = "debug_test_resources.jar"
task "compile_debugTestResources"(type: JavaCompile) {
source = fileTree(dir: resourcesDir, include: '**/*.java')
destinationDir = file("build/test/debugTestResources/classes")
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
options.compilerArgs += ["-g", "-Xlint:-options"]
}
task "jar_debugTestResources"(type: Jar, dependsOn: "compile_debugTestResources") {
archiveName = hostJar
destinationDir = file("build/test/")
from "build/test/debugTestResources/classes"
include "**/*.class"
}
def java8ResourcesDir = file("src/test/debugTestResourcesJava8")
def java8HostJar = "debug_test_resources_java8.jar"
task "compile_debugTestResourcesJava8"(type: JavaCompile) {
source = fileTree(dir: java8ResourcesDir, include: '**/*.java')
destinationDir = file("build/test/debugTestResourcesJava8/classes")
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
options.compilerArgs += ["-g", "-Xlint:-options"]
}
task "jar_debugTestResourcesJava8"(type: Jar, dependsOn: "compile_debugTestResourcesJava8") {
archiveName = java8HostJar
destinationDir = file("build/test/")
from "build/test/debugTestResourcesJava8/classes"
include "**/*.class"
}
dependsOn downloadDeps
dependsOn jar_debugTestResources
dependsOn jar_debugTestResourcesJava8
}
// Examples used by tests, where Android specific APIs are used.
task buildExampleAndroidApi(type: JavaCompile) {
source = fileTree(dir: file("src/test/examplesAndroidApi"), include: "**/*.java")
destinationDir = file("build/test/examplesAndroidApi/classes")
classpath = files("third_party/android_jar/lib-v26/android.jar")
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
task buildProtoGeneratedSources {
def examplesProtoDir = file("src/test/examplesProto")
examplesProtoDir.eachDir { dir ->
def name = dir.getName()
task "compile_proto_generated_source_${name}"(type: JavaCompile) {
source = {
file('third_party/proto').listFiles()
.findAll { it.name.startsWith(name) && it.name.endsWith('-src.jar') }
.collect { zipTree(it) }
}
destinationDir = file("build/generated/test/proto/${name}_classes")
classpath = files("third_party/protobuf-lite/libprotobuf_lite.jar")
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
task "jar_proto_generated_source_${name}"(type: Jar, dependsOn: "compile_proto_generated_source_${name}") {
archiveName = "${name}.jar"
destinationDir = file("build/generated/test/proto")
from "build/generated/test/proto/${name}_classes"
include "/**/*.class"
}
dependsOn "jar_proto_generated_source_${name}"
}
}
task buildExamplesProto {
def examplesProtoDir = file("src/test/examplesProto")
def examplesProtoOutputDir = file("build/test/examplesProto");
dependsOn buildProtoGeneratedSources
task "compile_examples_proto"(type: JavaCompile) {
source = fileTree(dir: examplesProtoDir, include: "**/*.java")
destinationDir = file("build/test/examplesProto/classes")
classpath = files("third_party/protobuf-lite/libprotobuf_lite.jar")
classpath += fileTree(dir: "build/generated/test/proto", include: "*.jar")
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
examplesProtoDir.eachDir { dir ->
def name = dir.getName()
task "jar_examples_proto_${name}"(type: Jar, dependsOn: "compile_examples_proto") {
archiveName = "${name}.jar"
destinationDir = examplesProtoOutputDir
from "build/test/examplesProto/classes"
include name + "/**/*.class"
}
dependsOn "jar_examples_proto_${name}"
}
}
// Proto lite generated code yields warnings when compiling with javac.
// We change the options passed to javac to ignore it.
compileExamplesJava.options.compilerArgs = ["-Xlint:none"]
task buildExampleJars {
dependsOn downloadProguard
def examplesDir = file("src/test/examples")
def proguardScript
if (OperatingSystem.current().isWindows()) {
proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.bat"
} else {
proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.sh"
}
task extractExamplesRuntime(type: Sync) {
dependsOn configurations.examplesRuntime
from { configurations.examplesRuntime.collect { zipTree(it) } }
include "**/*.class"
includeEmptyDirs false
into "$buildDir/runtime/examples/"
}
task "copy_examples_resources"(type: org.gradle.api.tasks.Copy) {
from examplesDir
exclude "**/*.java"
exclude "**/keep-rules*.txt"
into file("build/test/examples/classes")
}
task "compile_examples"(type: JavaCompile) {
dependsOn "copy_examples_resources"
source examplesDir
include "**/*.java"
destinationDir = file("build/test/examples/classes")
classpath = sourceSets.examples.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
options.compilerArgs = ["-g:source,lines", "-Xlint:none"]
}
task "compile_examples_debuginfo_all"(type: JavaCompile) {
source examplesDir
include "**/*.java"
destinationDir = file("build/test/examples/classes_debuginfo_all")
classpath = sourceSets.examples.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
options.compilerArgs = ["-g", "-Xlint:none"]
}
task "compile_examples_debuginfo_none"(type: JavaCompile) {
source examplesDir
include "**/*.java"
destinationDir = file("build/test/examples/classes_debuginfo_none")
classpath = sourceSets.examples.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
options.compilerArgs = ["-g:none", "-Xlint:none"]
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def exampleOutputDir = file("build/test/examples");
def jarName = "${name}.jar"
dependsOn "jar_example_${name}"
dependsOn "jar_example_${name}_debuginfo_all"
dependsOn "jar_example_${name}_debuginfo_none"
dependsOn "extractExamplesRuntime"
def runtimeDependencies = copySpec { }
// The "throwing" test verifies debugging/stack info on the post-proguarded output.
def proguardConfigPath = "${dir}/proguard.cfg"
if (new File(proguardConfigPath).exists()) {
task "pre_proguard_example_${name}"(type: Jar, dependsOn: "compile_examples") {
archiveName = "${name}_pre_proguard.jar"
destinationDir = exampleOutputDir
from "build/test/examples/classes"
include name + "/**/*.class"
with runtimeDependencies
includeEmptyDirs false
}
def jarPath = files(tasks.getByPath("pre_proguard_example_${name}")).files.first();
def proguardJarPath = "${exampleOutputDir}/${jarName}"
def proguardMapPath = "${exampleOutputDir}/${name}/${name}.map"
task "jar_example_${name}"(type: Exec, dependsOn: "pre_proguard_example_${name}") {
inputs.files files(
tasks.getByPath("pre_proguard_example_${name}"),
proguardConfigPath)
// Enable these to get stdout and stderr redirected to files...
// standardOutput = new FileOutputStream('proguard.stdout')
// errorOutput = new FileOutputStream('proguard.stderr')
def proguardArguments = "-verbose -dontwarn java.** -injars ${jarPath}" +
" -outjars ${proguardJarPath}" +
" -include ${proguardConfigPath}" +
" -printmapping ${proguardMapPath}"
if (OperatingSystem.current().isWindows()) {
executable "${proguardScript}"
args "${proguardArguments}"
} else {
executable "bash"
args "-c", "${proguardScript} '${proguardArguments}'"
}
outputs.file proguardJarPath
}
// TODO: Consider performing distinct proguard compilations.
task "jar_example_${name}_debuginfo_all"(type: Copy, dependsOn: "jar_example_${name}") {
from "${exampleOutputDir}/${name}.jar"
into "${exampleOutputDir}"
rename(".*", "${name}_debuginfo_all.jar")
}
task "jar_example_${name}_debuginfo_none"(type: Copy, dependsOn: "jar_example_${name}") {
from "${exampleOutputDir}/${name}.jar"
into "${exampleOutputDir}"
rename(".*", "${name}_debuginfo_none.jar")
}
} else {
task "jar_example_${name}"(type: Jar, dependsOn: "compile_examples") {
archiveName = "${name}.jar"
destinationDir = exampleOutputDir
from "build/test/examples/classes"
include name + "/**/*"
with runtimeDependencies
includeEmptyDirs true
}
task "jar_example_${name}_debuginfo_all"(type: Jar, dependsOn: "compile_examples_debuginfo_all") {
archiveName = "${name}_debuginfo_all.jar"
destinationDir = exampleOutputDir
from "build/test/examples/classes_debuginfo_all"
include name + "/**/*.class"
with runtimeDependencies
includeEmptyDirs false
}
task "jar_example_${name}_debuginfo_none"(type: Jar, dependsOn: "compile_examples_debuginfo_none") {
archiveName = "${name}_debuginfo_none.jar"
destinationDir = exampleOutputDir
from "build/test/examples/classes_debuginfo_none"
include name + "/**/*.class"
with runtimeDependencies
includeEmptyDirs false
}
}
}
}
task buildExampleAndroidNJars {
dependsOn downloadDeps
def examplesDir = file("src/test/examplesAndroidN")
task "compile_examplesAndroidN"(type: JavaCompile) {
source = fileTree(dir: examplesDir, include: '**/*.java')
destinationDir = file("build/test/examplesAndroidN/classes")
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
options.compilerArgs += ["-Xlint:-options"]
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def exampleOutputDir = file("build/test/examplesAndroidN");
def jarName = "${name}.jar"
dependsOn "jar_examplesAndroidN_${name}"
task "jar_examplesAndroidN_${name}"(type: Jar, dependsOn: "compile_examplesAndroidN") {
archiveName = jarName
destinationDir = exampleOutputDir
from "build/test/examplesAndroidN/classes"
include "**/" + name + "/**/*.class"
}
}
}
task buildExampleAndroidOJars {
dependsOn downloadDeps
def examplesDir = file("src/test/examplesAndroidO")
// NOTE: we want to enable a scenario when test needs to reference some
// classes generated by legacy (1.6) Java compiler to test some specific
// behaviour. To do so we compile all the java files located in sub-directory
// called 'legacy' with Java 1.6, then compile the rest of the files with
// Java 1.8 and a reference to previously generated 1.6 classes.
// Compiling all classes in dirs 'legacy' with old Java version.
task "compile_examplesAndroidO_Legacy"(type: JavaCompile) {
source = fileTree(dir: examplesDir, include: '**/legacy/**/*.java')
destinationDir = file("build/test/examplesAndroidOLegacy/classes")
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
options.compilerArgs += ["-Xlint:-options", "-parameters"]
}
// Compiling the rest of the files as Java 1.8 code.
task "compile_examplesAndroidO"(type: JavaCompile) {
dependsOn "compile_examplesAndroidO_Legacy"
source = fileTree(dir: examplesDir, include: '**/*.java', exclude: '**/legacy/**/*.java')
destinationDir = file("build/test/examplesAndroidO/classes")
classpath = sourceSets.main.compileClasspath
classpath += files("build/test/examplesAndroidOLegacy/classes")
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
options.compilerArgs += ["-Xlint:-options", "-parameters"]
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def destinationDir = file("build/test/examplesAndroidO/classes");
if (file("src/test/examplesAndroidO/" + name + "/TestGenerator.java").isFile()) {
task "generate_examplesAndroidO_${name}"(type: JavaExec,
dependsOn: "compile_examplesAndroidO") {
main = name + ".TestGenerator"
classpath = files(destinationDir, sourceSets.main.compileClasspath)
args destinationDir
}
} else {
task "generate_examplesAndroidO_${name}" () {}
}
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def exampleOutputDir = file("build/test/examplesAndroidO");
def jarName = "${name}.jar"
dependsOn "jar_examplesAndroidO_${name}"
task "jar_examplesAndroidO_${name}"(type: Jar, dependsOn: ["compile_examplesAndroidO",
"generate_examplesAndroidO_${name}"]) {
archiveName = jarName
destinationDir = exampleOutputDir
from "build/test/examplesAndroidO/classes" // Java 1.8 classes
from "build/test/examplesAndroidOLegacy/classes" // Java 1.6 classes
include "**/" + name + "/**/*.class"
// Do not include generator into the test runtime jar, it is not useful.
// Otherwise, shrinking will need ASM jars.
exclude "**/TestGenerator*"
}
}
}
task buildExampleAndroidPJars {
dependsOn downloadDeps
def examplesDir = file("src/test/examplesAndroidP")
task "compile_examplesAndroidP"(type: JavaCompile) {
source = fileTree(dir: examplesDir, include: '**/*.java')
destinationDir = file("build/test/examplesAndroidP/classes")
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
options.compilerArgs += ["-Xlint:-options"]
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def destinationDir = file("build/test/examplesAndroidP/classes");
if (file("src/test/examplesAndroidP/" + name + "/TestGenerator.java").isFile()) {
task "generate_examplesAndroidP_${name}"(type: JavaExec,
dependsOn: "compile_examplesAndroidP") {
main = name + ".TestGenerator"
classpath = files(destinationDir, sourceSets.main.compileClasspath)
args destinationDir
}
} else {
task "generate_examplesAndroidP_${name}" () {}
}
}
examplesDir.eachDir { dir ->
def name = dir.getName();
def exampleOutputDir = file("build/test/examplesAndroidP");
def jarName = "${name}.jar"
dependsOn "jar_examplesAndroidP_${name}"
task "jar_examplesAndroidP_${name}"(type: Jar,
dependsOn: ["compile_examplesAndroidP",
"generate_examplesAndroidP_${name}"]) {
archiveName = jarName
destinationDir = exampleOutputDir
from "build/test/examplesAndroidP/classes" // Java 1.8 classes
include "**/" + name + "/**/*.class"
// Do not include generator into the test runtime jar, it is not useful.
// Otherwise, shrinking will need ASM jars.
exclude "**/TestGenerator*"
}
}
}
def buildExampleJarsCreateTask(javaVersion, sourceSet) {
return tasks.create("buildExample${javaVersion}Jars") {
def examplesDir = file("src/test/examples${javaVersion}")
examplesDir.eachDir { dir ->
def name = dir.getName();
def exampleOutputDir = file("build/test/examples${javaVersion}");
def jarName = "${name}.jar"
dependsOn "jar_examples${javaVersion}_${name}"
task "jar_examples${javaVersion}_${name}"(type: Jar) {
archiveName = jarName
destinationDir = exampleOutputDir
from sourceSet.output
include "**/" + name + "/**/*.class"
}
}
}
}
buildExampleJarsCreateTask("Java9", sourceSets.examplesJava9)
buildExampleJarsCreateTask("Java10", sourceSets.examplesJava10)
buildExampleJarsCreateTask("Java11", sourceSets.examplesJava11)
buildExampleJarsCreateTask("Java17", sourceSets.examplesJava17)
buildExampleJarsCreateTask("Java18", sourceSets.examplesJava18)
task provideArtFrameworksDependencies {
cloudDependencies.tools.forEach({ art ->
if (art.contains("art")) {
def taskName = art.replace('/','_')
dependsOn "patch_${taskName}"
task "patch_${taskName}"(type: org.gradle.api.tasks.Copy){
from "tools/${art}/framework"
include "**.jar"
into file("tools/${art}/out/host/linux-x86/framework")
}
}
})
}
task buildKotlinR8TestResources {
def examplesDir = file("src/test/kotlinR8TestResources")
examplesDir.eachDir { dir ->
kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
def name = dir.getName()
def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
def javaOutputJarName = "${name}.java.jar"
def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
task "${taskName}Java"(type: JavaCompile) {
source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.java')
destinationDir = file(javaOutput)
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
options.compilerArgs += ["-g", "-Xlint:-options"]
}
task "${taskName}JavaJar"(type: Jar, dependsOn: "${taskName}Java") {
archiveName = javaOutputJarName
destinationDir = file(javaOutputJarDir)
from javaOutput
include "**/*.class"
}
dependsOn "${taskName}JavaJar"
}
}
}
task buildExamples {
if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " +
"platform (" + OperatingSystem.current().getName() + ").")
} else if (!OperatingSystem.current().isLinux()) {
logger.lifecycle("WARNING: Testing (including building examples) is not supported on your platform. " +
"It is fully supported on Linux and partially supported on Mac OS and Windows")
return;
}
dependsOn buildDebugTestResourcesJars
dependsOn buildExampleJars
dependsOn buildExampleAndroidNJars
dependsOn buildExampleAndroidOJars
dependsOn buildExampleAndroidPJars
dependsOn buildExampleJava9Jars
dependsOn buildExampleJava10Jars
dependsOn buildExampleJava11Jars
dependsOn buildExampleJava17Jars
dependsOn buildExampleJava18Jars
dependsOn buildExampleAndroidApi
def examplesDir = file("src/test/examples")
def noDexTests = [
"multidex",
"multidex002",
"multidex004",
]
examplesDir.eachDir { dir ->
def name = dir.getName();
if (!(name in noDexTests)) {
dependsOn "dex_example_${name}"
def exampleOutputDir = file("build/test/examples/" + name);
def dexPath = file("${exampleOutputDir}")
def debug = (name == "throwing")
if (!dexPath.exists()) {
dexPath.mkdirs()
}
task "dex_example_${name}"(type: DxTask, dependsOn: "jar_example_${name}") {
source = files(tasks.getByPath("jar_example_${name}")).asFileTree
destination = dexPath
debug = debug
}
}
}
}
task buildSmali {
def smaliDir = file("src/test/smali")
smaliDir.eachDirRecurse() { dir ->
def name = dir.getName();
def relativeDir = smaliDir.toPath().relativize(dir.toPath());
def smaliOutputDir = file("build/test/smali/" + relativeDir);
smaliOutputDir.mkdirs()
outputs.dir smaliOutputDir
def taskName = "smali_build_${relativeDir.toString().replace('/', '_').replace('\\', '_')}"
def smaliFiles = fileTree(dir: dir, include: '*.smali')
def javaFiles = fileTree(dir: dir, include: '*.java')
def destDir = smaliOutputDir;
def destFile = destDir.toPath().resolve("${name}.dex").toFile()
def intermediateFileName = "${name}-intermediate.dex";
def intermediateFile = destDir.toPath().resolve(intermediateFileName).toFile()
if (javaFiles.empty) {
if (!smaliFiles.empty) {
dependsOn "${taskName}_smali"
task "${taskName}_smali"(type: SmaliTask) {
source = smaliFiles
destination = destFile
}
}
} else {
dependsOn "${taskName}_dexmerger"
task "${taskName}_smali"(type: SmaliTask) {
source = smaliFiles
destination = intermediateFile
}
task "${taskName}_java"(type: JavaCompile) {
source = javaFiles
destinationDir destDir
classpath = sourceSets.main.compileClasspath
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
options.compilerArgs += ["-Xlint:-options"]
}
task "${taskName}_jar"(type: Jar, dependsOn: "${taskName}_java") {
archiveName = "Test.jar"
destinationDir = destDir
from fileTree(dir: destDir, include: 'Test.class')
}
task "${taskName}_dx"(type: DxTask, dependsOn: "${taskName}_jar") {
source = fileTree(dir: destDir, include: 'Test.jar')
destination = destDir
}
task "${taskName}_dexmerger"(
type: DexMergerTask, dependsOn: ["${taskName}_dx", "${taskName}_smali"]) {
source = fileTree(dir: destDir, include: ["classes.dex", intermediateFileName])
destination = destFile
}
}
}
}
tasks.withType(Test) {
println("NOTE: Number of processors " + Runtime.runtime.availableProcessors())
def userDefinedCoresPerFork = System.getenv('R8_GRADLE_CORES_PER_FORK')
def processors = Runtime.runtime.availableProcessors()
// See https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html.
if (userDefinedCoresPerFork) {
maxParallelForks = processors.intdiv(userDefinedCoresPerFork.toInteger()) ?: 1
} else {
// On normal work machines this seems to give the best test execution time (without freezing)
maxParallelForks = processors.intdiv(3) ?: 1
// On low cpu count machines (bots) we under subscribe, so increase the count.
if (processors == 8) {
maxParallelForks = 3
}
}
println("NOTE: Max parallel forks " + maxParallelForks)
forkEvery = 0
if (project.hasProperty('disable_assertions')) {
enableAssertions = false
}
// TODO(b/124091860): Increase the max heap size to avoid OOM when running tests.
if (project.hasProperty('test_xmx')) {
maxHeapSize = project.property('test_xmx')
} else {
maxHeapSize = "4G"
}
}
task buildPreNJdwpTestsJar(type: Jar) {
archiveFileName = 'jdwp-tests-preN.jar'
from zipTree('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
// Exclude the classes containing java8
exclude 'org/apache/harmony/jpda/tests/jdwp/InterfaceType/*.class'
exclude 'org/apache/harmony/jpda/tests/jdwp/ObjectReference/InvokeMethodDefault*.class'
includeEmptyDirs = false
}
task buildPreNJdwpTestsDex(type: Exec, dependsOn: "buildPreNJdwpTestsJar") {
def inFile = buildPreNJdwpTestsJar.archivePath
def outFile = new File(buildPreNJdwpTestsJar.destinationDir, buildPreNJdwpTestsJar.baseName + '-dex.jar')
inputs.files files(inFile)
outputs.file outFile
if (OperatingSystem.current().isWindows()) {
executable file("tools/windows/dx/bin/dx.bat")
} else if (OperatingSystem.current().isMacOsX()) {
executable file("tools/mac/dx/bin/dx");
} else {
executable file("tools/linux/dx/bin/dx");
}
args "--dex"
args "--output=${outFile}"
args inFile
}
task getJarsFromSupportLibs(type: GetJarsFromConfiguration) {
setConfiguration(configurations.supportLibs)
}
task generateR8TestKeepRules {
def path = "build/generated/r8tests-keep.txt"
outputs.file path
dependsOn R8Lib
doLast {
file(path).write """-keep class ** { *; }
-dontshrink
-dontoptimize
-keepattributes *
-applymapping ${R8Lib.outputs.files[0]}.map
"""
}
}
task buildR8LibCfTestDeps(type: Exec) {
def outputPath = "build/libs/r8libtestdeps-cf.jar"
dependsOn downloadDeps
dependsOn r8NoManifest
dependsOn R8Lib
dependsOn generateR8TestKeepRules
dependsOn testJar
// Take all .jar files as libraries and append the generated test classes in classes/java/test.
def addedLibraries = sourceSets.test.runtimeClasspath.findAll { pkg ->
return pkg.toString().endsWith(".jar")
} + ["${buildDir}/classes/java/test"]
inputs.files testJar.outputs.files +
generateR8TestKeepRules.outputs.files +
R8Lib.outputs
commandLine = r8CfCommandLine(
testJar.outputs.files[0],
outputPath,
[generateR8TestKeepRules.outputs.files[0]],
["--debug", "--classpath", r8NoManifest.outputs.files[0]],
r8NoManifest.outputs.files + addedLibraries)
workingDir = projectDir
outputs.file outputPath
}
task configureTestForR8Lib(type: Copy) {
dependsOn testJar
inputs.files buildR8LibCfTestDeps.outputs
dependsOn R8Lib
delete r8LibTestPath
from zipTree(buildR8LibCfTestDeps.outputs.files[0])
def examplesDir = file("build/test")
examplesDir.eachDir { dir ->
from ("${buildDir}/test/${dir.getName()}/classes")
}
from ("${buildDir}/runtime/examples")
into r8LibTestPath
outputs.dir r8LibTestPath
}
def shouldRetrace() {
return project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')
}
def retrace(Throwable exception) {
def out = new StringBuffer()
def err = new StringBuffer()
def command = "python3 tools/retrace.py --quiet"
def header = "RETRACED STACKTRACE";
out.append("\n--------------------------------------\n")
out.append("${header}\n")
out.append("--------------------------------------\n")
Process process = command.execute()
def processIn = new PrintStream(process.getOut())
process.consumeProcessOutput(out, err)
exception.printStackTrace(processIn)
processIn.flush()
processIn.close()
def errorDuringRetracing = process.waitFor() != 0
if (errorDuringRetracing) {
out.append("ERROR DURING RETRACING\n")
out.append(err.toString())
}
if (project.hasProperty('print_obfuscated_stacktraces') || errorDuringRetracing) {
out.append("\n\n--------------------------------------\n")
out.append("OBFUSCATED STACKTRACE\n")
out.append("--------------------------------------\n")
}
return out.toString()
}
def printStackTrace(TestResult result) {
filterStackTraces(result)
if (shouldRetrace()) {
def exception = new Exception(retrace(result.exception))
exception.setStackTrace([] as StackTraceElement[])
result.exceptions.add(0, exception)
}
}
def filterStackTraces(TestResult result) {
for (Throwable throwable : result.getExceptions()) {
filterStackTrace(throwable)
}
}
// It would be nice to do this in a non-destructive way...
def filterStackTrace(Throwable exception) {
if (!project.hasProperty('print_full_stacktraces')) {
def elements = []
def skipped = []
for (StackTraceElement element : exception.getStackTrace()) {
if (element.toString().contains("com.android.tools.r8")) {
elements.addAll(skipped)
elements.add(element)
skipped.clear()
} else {
skipped.add(element)
}
}
exception.setStackTrace(elements as StackTraceElement[])
}
}
def printAllStackTracesToFile(List<Throwable> exceptions, File out) {
new PrintStream(new FileOutputStream(out)).withCloseable {printer ->
exceptions.forEach { it.printStackTrace(printer) }
}
}
static def escapeHtml(String string) {
return string.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
}
static def urlEncode(String string) {
// Not sure why, but the + also needs to be converted to have working links.
return URLEncoder.encode(string, "UTF-8").replace("+","%20")
}
def ensureDir(File dir) {
dir.mkdirs()
return dir
}
// Some of our test parameters have new lines :-( We really don't want test names to span lines.
static def sanitizedTestName(testDesc) {
if (testDesc.getName().contains("\n")) {
throw new RuntimeException("Unsupported use of newline in test name: '${testDesc.getName()}'")
}
return testDesc.getName()
}
static def desanitizedTestName(testName) {
return testName
}
def getTestReportEntryDir(reportDir, testDesc) {
return ensureDir(reportDir.toPath()
.resolve(testDesc.getClassName())
.resolve(sanitizedTestName(testDesc))
.toFile())
}
def getTestReportEntryURL(reportDir, testDesc) {
def classDir = urlEncode(testDesc.getClassName())
def testDir = urlEncode(sanitizedTestName(testDesc))
return "file://${reportDir}/${classDir}/${testDir}"
}
def getTestResultEntryOutputFile(reportDir, testDesc, fileName) {
def dir = getTestReportEntryDir(reportDir, testDesc).toPath()
return dir.resolve(fileName).toFile()
}
def withTestResultEntryWriter(reportDir, testDesc, fileName, append, fn) {
def file = getTestResultEntryOutputFile(reportDir, testDesc, fileName)
new FileWriter(file, append).withCloseable fn
}
static def getGitBranchName() {
def out = new StringBuilder()
def err = new StringBuilder()
def proc = "git rev-parse --abbrev-ref HEAD".execute()
proc.waitForProcessOutput(out, err)
return out.toString().trim()
}
static def getFreshTestReportIndex(File reportDir) {
def number = 0
while (true) {
def freshIndex = reportDir.toPath().resolve("index.${number++}.html").toFile()
if (!freshIndex.exists()) {
return freshIndex
}
}
}
def forEachTestReportAlreadyX(File reportDir, fileName, onTest) {
def out = new StringBuilder()
def err = new StringBuilder()
def proc = "find . -name ${fileName}".execute([], reportDir)
proc.waitForProcessOutput(out, err)
def outString = out.toString()
outString.eachLine {
// Lines are of the form: ./<class>/<name>/FAILURE
def clazz = null
def name = null
try {
def trimmed = it.trim()
def line = trimmed.substring(2)
def sep = line.indexOf("/")
clazz = line.substring(0, sep)
name = line.substring(sep + 1, line.length() - fileName.length() - 1)
} catch (Exception e) {
logger.lifecycle("WARNING: failed attempt to read test description from: '${it}'")
return
}
onTest(clazz, desanitizedTestName(name))
}
return !outString.trim().isEmpty()
}
def forEachTestReportAlreadyFailing(File reportDir, onFailureTest) {
return forEachTestReportAlreadyX(reportDir, TestResult.ResultType.FAILURE.name(), onFailureTest)
}
def forEachTestReportAlreadyPassing(File reportDir, onSucceededTest) {
return forEachTestReportAlreadyX(reportDir, TestResult.ResultType.SUCCESS.name(), onSucceededTest)
}
def forEachTestReportAlreadySkipped(File reportDir, onSucceededTest) {
return forEachTestReportAlreadyX(reportDir, TestResult.ResultType.SKIPPED.name(), onSucceededTest)
}
def setUpTestingState(Test task) {
// Hide all test events from the console, they are written to the report.
task.testLogging { events = [] }
def branch = project.hasProperty('testing-state-name')
? project.getProperty('testing-state-name')
: getGitBranchName()
def reportDir = file("${buildDir}/test-state/${branch}")
def index = reportDir.toPath().resolve("index.html").toFile()
def resetState = project.hasProperty('reset-testing-state')
def reportDirExists = reportDir.exists()
def resuming = !resetState && reportDirExists
def hasFailingTests = false;
if (resuming) {
// Test filtering happens before the test execution is initiated so compute it here.
// If there are still failing tests in the report, include only those.
hasFailingTests = forEachTestReportAlreadyFailing(reportDir, {
clazz, name -> task.filter.includeTestsMatching("$clazz.$name")
})
// Otherwise exclude all of the test already marked as succeeding.
if (!hasFailingTests) {
// Also allow the test to overall succeed if there are no remaining tests that match,
// which is natural if the state already succeeded in full.
task.filter.failOnNoMatchingTests = false
forEachTestReportAlreadyPassing(reportDir, {
clazz, name -> task.filter.excludeTestsMatching("$clazz.$name")
})
forEachTestReportAlreadySkipped(reportDir, {
clazz, name -> task.filter.excludeTestsMatching("$clazz.$name")
})
}
}
task.beforeSuite { desc ->
if (!desc.parent) {
def parentReport = null
if (resetState && reportDirExists) {
delete reportDir
}
if (resuming) {
if (index.exists()) {
parentReport = getFreshTestReportIndex(reportDir)
index.renameTo(parentReport)
}
} else {
reportDir.mkdirs()
}
def runPrefix = resuming ? "Resuming" : "Starting"
def title = "${runPrefix} @ ${branch}"
// Print a console link to the test report for easy access.
println "${runPrefix} test, report written to:"
println " file://${index}"
// Print the new index content.
index << "<html><head><title>${title}</title>"
index << "<style> * { font-family: monospace; }</style>"
index << "<meta http-equiv='refresh' content='10' />"
index << "</head><body><h1>${title}</h1>"
index << "<p>Run on: ${new Date()}</p>"
index << "<p>Git branch: ${branch}</p>"
if (parentReport != null) {
index << "<p><a href=\"file://${parentReport}\">Previous result index</a></p>"
}
index << "<p><a href=\"file://${index}\">Most recent result index</a></p>"
index << "<p><a href=\"file://${reportDir}\">Test directories</a></p>"
index << "<h2>Failing tests (refreshing automatically every 10 seconds)</h2><ul>"
}
}
task.afterSuite { desc, result ->
if (!desc.parent) {
// Update the final test results in the index.
index << "</ul>"
if (result.resultType == TestResult.ResultType.SUCCESS) {
if (hasFailingTests) {
index << "<h2>Rerun of failed tests now pass!</h2>"
index << "<h2>Rerun again to continue with outstanding tests!</h2>"
} else {
index << "<h2 style=\"background-color:#62D856\">GREEN BAR == YOU ROCK!</h2>"
}
} else if (result.resultType == TestResult.ResultType.FAILURE) {
index << "<h2 style=\"background-color:#6D130A\">Some tests failed: ${result.resultType.name()}</h2><ul>"
} else {
index << "<h2>Tests finished: ${result.resultType.name()}</h2><ul>"
}
index << "<li>Number of tests: ${result.testCount}"
index << "<li>Failing tests: ${result.failedTestCount}"
index << "<li>Successful tests: ${result.successfulTestCount}"
index << "<li>Skipped tests: ${result.skippedTestCount}"
index << "</ul></body></html>"
}
}
// Events to stdout/err are appended to the files in the test directories.
task.onOutput { desc, event ->
withTestResultEntryWriter(reportDir, desc, event.getDestination().name(), true, {
it.append(event.getMessage())
})
}
task.beforeTest { desc ->
// Remove any stale output files before running the test.
for (def destType : TestOutputEvent.Destination.values()) {
def destFile = getTestResultEntryOutputFile(reportDir, desc, destType.name())
if (destFile.exists()) {
delete destFile
}
}
}
task.afterTest { desc, result ->
if (result.getTestCount() != 1) {
throw new IllegalStateException("Unexpected test with more than one result: ${desc}")
}
// Clear any previous result files.
for (def resultType : TestResult.ResultType.values()) {
delete getTestResultEntryOutputFile(reportDir, desc, resultType.name())
}
// Emit the result type status in a file of the same name: SUCCESS, FAILURE or SKIPPED.
withTestResultEntryWriter(reportDir, desc, result.getResultType().name(), false, {
it.append(result.getResultType().name())
})
// Emit the test time.
withTestResultEntryWriter(reportDir, desc, "time", false, {
it.append("${result.getEndTime() - result.getStartTime()}")
})
// For failed tests, update the index and emit stack trace information.
if (result.resultType == TestResult.ResultType.FAILURE) {
def title = escapeHtml("${desc.className}.${desc.name}")
def link = getTestReportEntryURL(reportDir, desc)
index << "<li><a href=\"${link}\">${title}</a></li>"
if (!result.exceptions.isEmpty()) {
printAllStackTracesToFile(
result.exceptions,
getTestResultEntryOutputFile(
reportDir,
desc,
"exceptions-raw.txt"))
filterStackTraces(result)
printAllStackTracesToFile(
result.exceptions,
getTestResultEntryOutputFile(
reportDir,
desc,
"exceptions-filtered.txt"))
if (shouldRetrace()) {
withTestResultEntryWriter(reportDir, desc, "exceptions-retraced.txt", false, { writer ->
result.exceptions.forEach { writer.append(retrace(it)) }
})
}
}
}
}
}
def testTimes = [:]
def numberOfTestTimesToPrint = 100
test { task ->
dependsOn buildLibraryDesugarConversions
dependsOn getJarsFromSupportLibs
// R8.jar is required for running bootstrap tests.
dependsOn r8
def useTestingState = project.hasProperty('testing-state')
if (useTestingState) {
setUpTestingState(task)
}
if (project.hasProperty('generate_golden_files_to')) {
systemProperty 'generate_golden_files_to', project.property('generate_golden_files_to')
assert project.hasProperty('HEAD_sha1')
systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
}
if (project.hasProperty('use_golden_files_in')) {
systemProperty 'use_golden_files_in', project.property('use_golden_files_in')
assert project.hasProperty('HEAD_sha1')
systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
}
if (project.hasProperty('kotlin_compiler_dev')) {
systemProperty 'com.android.tools.r8.kotlincompilerdev', '1';
}
if (project.hasProperty('kotlin_compiler_old')) {
systemProperty 'com.android.tools.r8.kotlincompilerold', '1';
}
if (!useTestingState) {
testLogging.exceptionFormat = 'full'
if (project.hasProperty('print_test_stdout')) {
testLogging.showStandardStreams = true
}
}
if (project.hasProperty('dex_vm') && project.property('dex_vm') != 'default') {
println "NOTE: Running with non default vm: " + project.property('dex_vm')
systemProperty 'dex_vm', project.property('dex_vm')
}
// Forward runtime configurations for test parameters.
if (project.hasProperty('runtimes')) {
println "NOTE: Running with runtimes: " + project.property('runtimes')
systemProperty 'runtimes', project.property('runtimes')
}
if (project.hasProperty('slow_tests')) {
systemProperty 'slow_tests', project.property('slow_tests')
}
if (project.hasProperty('desugar_jdk_json_dir')) {
systemProperty 'desugar_jdk_json_dir', project.property('desugar_jdk_json_dir')
}
if (project.hasProperty('desugar_jdk_libs')) {
systemProperty 'desugar_jdk_libs', project.property('desugar_jdk_libs')
}
if (!useTestingState) {
if (project.hasProperty('print_times') || project.hasProperty('one_line_per_test')) {
afterTest { desc, result ->
def executionTime = (result.endTime - result.startTime) / 1000
testTimes["${desc.name} [${desc.className}]"] = executionTime
}
afterSuite { desc, result ->
// parent is null if all tests are done.
if (desc.parent == null) {
def sortedTimes = testTimes.sort({ e1, e2 -> e2.value <=> e1.value })
sortedTimes.eachWithIndex { key, value, i ->
if (i < numberOfTestTimesToPrint) println "$key: $value"
}
}
}
}
if (project.hasProperty('one_line_per_test')) {
beforeTest { desc ->
println "Start executing test ${desc.name} [${desc.className}]"
}
afterTest { desc, result ->
if (result.resultType == TestResult.ResultType.FAILURE) {
printStackTrace(result)
}
if (project.hasProperty('update_test_timestamp')) {
file(project.getProperty('update_test_timestamp')).text = new Date().getTime()
}
println "Done executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
}
} else {
afterTest { desc, result ->
if (result.resultType == TestResult.ResultType.FAILURE) {
printStackTrace(result)
}
}
}
}
if (project.hasProperty('no_internal')) {
exclude "com/android/tools/r8/internal/**"
} else {
dependsOn buildExamplesProto
}
if (project.hasProperty('only_internal')) {
include "com/android/tools/r8/internal/**"
}
if (project.hasProperty('test_namespace')) {
include "com/android/tools/r8/" + project.getProperty('test_namespace') + "/**"
}
if (project.hasProperty('tool') && project.property('tool') == 'd8') {
// Don't run anything, deprecated
println "Running with deprecated tool d8, not running any tests"
include ""
}
if (!project.hasProperty('all_tests')) {
exclude "com/android/tools/r8/art/dx/**"
}
if (project.hasProperty('no_arttests')) {
exclude "com/android/tools/r8/art/**"
}
if (project.hasProperty('shard_count') ) {
assert project.hasProperty('shard_number')
int shard_count = project.getProperty('shard_count') as Integer
int shard_number = project.getProperty('shard_number') as Integer
assert shard_count < 65536
assert shard_number < shard_count
exclude {
entry ->
// Don't leave out directories. Leaving out a directory means all entries below.
if (entry.file.isDirectory()) {
return false
}
def first4 = entry.getRelativePath().toString().md5().substring(0, 4)
int hash = Integer.parseInt(first4, 16)
return hash % shard_count != shard_number
}
}
if (project.hasProperty('test_dir')) {
systemProperty 'test_dir', project.property('test_dir')
}
if (project.hasProperty('r8lib')) {
dependsOn configureTestForR8Lib
// R8lib should be used instead of the main output and all the tests in
// r8 should be mapped and exists in r8LibTestPath.
classpath = sourceSets.test.runtimeClasspath.filter {
!it.getAbsolutePath().contains("/build/")
}
classpath += files([r8LibPath, r8LibTestPath])
testClassesDirs = files(r8LibTestPath)
}
if (OperatingSystem.current().isLinux()
|| OperatingSystem.current().isMacOsX()
|| OperatingSystem.current().isWindows()) {
if (OperatingSystem.current().isMacOsX()) {
logger.lifecycle("WARNING: Testing in only partially supported on Mac OS. " +
"Art only runs on Linux and tests requiring Art runs in a Docker container, which must be present. " +
"See tools/docker/README.md for details.")
}
if (OperatingSystem.current().isWindows()) {
logger.lifecycle("WARNING: Testing in only partially supported on Windows. " +
"Art only runs on Linux and tests requiring Art will be skipped")
}
dependsOn downloadDeps
dependsOn buildExamples
dependsOn buildKotlinR8TestResources
dependsOn buildSmali
dependsOn buildPreNJdwpTestsJar
dependsOn buildPreNJdwpTestsDex
dependsOn compileTestNGRunner
dependsOn provideArtFrameworksDependencies
} else {
logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
"Linux and partially supported on Mac OS and Windows. Art does not run on other platforms.")
}
}
// The Art tests we use for R8 are pre-build and downloaded from Google Cloud Storage.
//
// To build and upload a new set of the Art tests for use with R8 follow these steps:
//
// First of all an Android checkout is required. Currently it must be located
// in $HOME/android/master.
//
// TODO(ricow): simplify this
//
// Before: update the checked in art, see scripts/update-host-art.sh
//
// 1. Get an android checkout in $HOME/android/master and apply the patch from
// https://android-review.googlesource.com/#/c/294187/
//
// 2. run the following commands in the Android checkout directory:
//
// source build/envsetup.sh
// lunch aosp_angler-userdebug # or lunch aosp_angler-eng
// m desugar
// m -j30 test-art-host
// DESUGAR=false ANDROID_COMPILE_WITH_JACK=false art/test.py --host -t 001-HelloWorld
//
// Without running the test.py command the classes.jar file used by desugar in
// $HOME/android/master/out/host/common/obj/JAVA_LIBRARIES/core-oj-hostdex_intermediates/
// seems to be missing - there is probably also a make target to build it more directly
//
// 3. In the R8 project root directory, make sure we have a clean state before starting:
// tools/gradle.py downloadDeps
// tools/gradle.py clean
// rm -rf tests/art
//
// 4. Now build in the R8 checkout (-P hack to not generate dirs when not running this target)
// Make sure you have smali on your path, please use the build binary in the
// out/host/linux-x86/bin directory of the android checkout. Currently this is version pre 2.2.1,
// if that is updated the call to smali in "task "${smaliToDexTask}"(type: Exec)" below might
// need to change as smali got a completely new command line interface in version 2.2.1.
// After Android O, Jack is no longer alive, do not forget to uncomment call to buildArtTest for
// Jack if you build an android version using Jack.
//
// PATH=$HOME/android/master/out/host/linux-x86/bin:$PATH tools/gradle.py -Pandroid_source buildArtTests
//
// 4a. If any failures are produced in step 4, figure out what went wrong and add an entry in
// skippedTests with an explanation. Rerun from step 3.
//
// 5. Run the tests:
// tools/gradle.py clean
// tools/test.py
//
// 5a. If any more tests fail, either fix the issue or add them to the failuresToTriage list (note
// that you need to change "_" to "-" from stdout). Rerun from step 5 if anything was added to
// failuresToTriage.
//
// 6. To upload a new version to Google Cloud Storage:
// cd tests
// upload_to_google_storage.py -a --bucket r8-deps art
//
// 7. Update the manifest file describing the Android repo used:
// repo manifest -o <r8-checkout-root>/tools/linux/aosp_master_manifest.xml -r
def androidCheckoutDir = file("${System.env.HOME}/android/master")
def artTestDir = file("${androidCheckoutDir}/art/test")
if (project.hasProperty('android_source')) {
task buildArtTests {
outputs.upToDateWhen { false }
def toBeTriaged = [
"903-hello-tagging",
"904-object-allocation",
"905-object-free",
"906-iterate-heap",
"907-get-loaded-classes",
"908-gc-start-finish",
"954-invoke-polymorphic-verifier",
"955-methodhandles-smali",
"596-monitor-inflation",
]
def skippedTests = toBeTriaged + [
// This test produces no jar.
"000-nop",
// This does not build, as it tests the error when the application exceeds more
// than 65536 methods
"089-many-methods",
// Requires some jack beta jar
"956-methodhandles",
]
def skippedTestsDx = [
// Tests with custom build scripts, where javac is not passed the options
// -source 1.7 -target 1.7.
"462-checker-inlining-across-dex-files",
"556-invoke-super",
"569-checker-pattern-replacement",
// These tests use jack even when --build-with-javac-dx is specified.
"004-JniTest",
"048-reflect-v8",
"146-bad-interface",
"563-checker-invoke-super",
"580-checker-string-fact-intrinsics", // java.lang.StringFactory
"604-hot-static-interface",
"957-methodhandle-transforms",
"958-methodhandle-emulated-stackframe",
"959-invoke-polymorphic-accessors",
"961-default-iface-resolution-gen",
"962-iface-static",
"963-default-range-smali",
"964-default-iface-init-gen",
"965-default-verify",
"966-default-conflict",
"967-default-ame",
"968-default-partial-compile-gen",
"969-iface-super",
"970-iface-super-resolution-gen",
"971-iface-super",
// These tests does not build with --build-with-javac-dx
"004-NativeAllocations", // Javac error
"031-class-attributes",
"138-duplicate-classes-check",
"157-void-class", // Javac error
"580-checker-string-factory-intrinsics",
"612-jit-dex-cache",
"613-inlining-dex-cache",
"900-hello-plugin", // --experimental agents
"901-hello-ti-agent", // --experimental agents
"902-hello-transformation", // --experimental agents
"909-attach-agent", // --experimental agents
"946-obsolete-throw", // -source 1.7 -target 1.7, but use lambda
"950-redefine-intrinsic", // -source 1.7 -target 1.7, but use method references
"951-threaded-obsolete", // -source 1.7 -target 1.7, but use lambda
"960-default-smali", // --experimental default-methods
// These tests force the build to use jack
"953-invoke-polymorphic-compiler",
"958-methodhandle-stackframe",
]
def artTestBuildDir = file("${projectDir}/tests/art")
if (androidCheckoutDir.exists()) {
dependsOn downloadDeps
artTestBuildDir.mkdirs()
artTestDir.eachDir { dir ->
def name = dir.getName();
def markerFile = dir.toPath().resolve("info.txt").toFile();
if (markerFile.exists() && !(name in skippedTests)) {
if (!(name in skippedTestsDx)) {
dependsOn buildArtTest(androidCheckoutDir, artTestBuildDir, dir);
}
}
}
}
doFirst {
if (!androidCheckoutDir.exists()) {
throw new InvalidUserDataException(
"This task requires an Android checkout in ${androidCheckoutDir}");
}
}
doLast {
copy {
from file("${androidCheckoutDir}/out/host/linux-x86/nativetest64")
into file("${artTestBuildDir}/lib64")
include 'lib*.so'
}
copy {
from file("${androidCheckoutDir}/out/host/linux-x86/lib64")
into file("${artTestBuildDir}/lib64")
include 'libart.so'
include 'libbacktrace.so'
include 'libbase.so'
include 'libc++.so'
include 'libcutils.so'
include 'liblz4.so'
include 'liblzma.so'
include 'libnativebridge.so'
include 'libnativeloader.so'
include 'libsigchain.so'
include 'libunwind.so'
include 'libziparchive.so'
}
copy {
from file("${androidCheckoutDir}/out/host/linux-x86/nativetest")
into file("${artTestBuildDir}/lib")
include 'lib*.so'
}
copy {
from file("${androidCheckoutDir}/out/host/linux-x86/lib")
into file("${artTestBuildDir}/lib")
include 'libart.so'
include 'libbacktrace.so'
include 'libbase.so'
include 'libc++.so'
include 'libcutils.so'
include 'liblz4.so'
include 'liblzma.so'
include 'libnativebridge.so'
include 'libnativeloader.so'
include 'libsigchain.so'
include 'libunwind.so'
include 'libziparchive.so'
}
}
}
}
def buildArtTest(androidCheckoutDir, artTestBuildDir, dir) {
def artTestDir = file("${androidCheckoutDir}/art/test")
def artRunTestScript = file("${artTestDir}/run-test")
def dxExecutable = new File("tools/linux/dx/bin/dx");
def dexMergerExecutable = Utils.dexMergerExecutable()
def name = dir.getName()
def buildTask = "build_art_test_dx_${name}"
def sanitizeTask = "sanitize_art_test_dx_${name}"
def copyCheckTask = "copy_check_art_test_dx_${name}"
def smaliToDexTask = "smali_to_dex_dx_${name}"
def buildInputs = fileTree(dir: dir, include: '**/*')
def testDir = file("${artTestBuildDir}/dx/${name}")
def outputJar = testDir.toPath().resolve("${name}.jar").toFile()
testDir.mkdirs()
task "$buildTask"(type: Exec) {
outputs.upToDateWhen { false }
inputs.file buildInputs
executable "${artRunTestScript}"
args "--host"
args "--build-only"
args "--build-with-javac-dx"
args "--output-path", "${testDir}"
args "${name}"
environment DX: "${dxExecutable.absolutePath}"
environment DXMERGER: "${dexMergerExecutable.absolutePath}"
environment ANDROID_BUILD_TOP: "${androidCheckoutDir}"
outputs.file outputJar
}
task "${sanitizeTask}"(type: Exec, dependsOn: buildTask) {
outputs.upToDateWhen { false }
executable "/bin/bash"
args "-c"
args "rm -rf ${testDir}/smali_*.dex ${testDir}/*-ex.dex ${testDir}/*-ex.jar" +
" ${testDir}/classes-ex ${testDir}/check"
}
task "${smaliToDexTask}"(type: Exec) {
// Directory that contains smali files is either smali, or smali/art
def smali_dir = file("${dir}/smali/art")
if (smali_dir.exists()) {
workingDir "${testDir}/smali/art"
} else {
workingDir "${testDir}/smali"
}
executable "/bin/bash"
// This is the command line options for smali prior to 2.2.1, where smali got a new
// command line interface.
args "-c", "smali a *.smali"
// This is the command line options for smali 2.2.1 and later.
// args "-c", "smali -o out.dex *.smali"
}
task "${copyCheckTask}"(type: Copy, dependsOn: sanitizeTask) {
def smali_dir = file("${dir}/smali")
outputs.upToDateWhen { false }
if (smali_dir.exists()) {
dependsOn smaliToDexTask
}
from("${artTestDir}/${name}") {
include 'check'
}
into testDir
}
return copyCheckTask
}
task javadocD8(type: Javadoc) {
title "D8 API"
classpath = sourceSets.main.compileClasspath
source = sourceSets.main.allJava
include '**/com/android/tools/r8/ArchiveClassFileProvider.java'
include '**/com/android/tools/r8/ArchiveProgramResourceProvider.java'
include '**/com/android/tools/r8/BaseCommand.java'
include '**/com/android/tools/r8/BaseCompilerCommand.java'
include '**/com/android/tools/r8/ClassFileResourceProvider.java'
include '**/com/android/tools/r8/CompilationFailedException.java'
include '**/com/android/tools/r8/CompilationMode.java'
include '**/com/android/tools/r8/D8.java'
include '**/com/android/tools/r8/D8Command.java'
include '**/com/android/tools/r8/DexIndexedConsumer.java'
include '**/com/android/tools/r8/DexFilePerClassFileConsumer.java'
include '**/com/android/tools/r8/Diagnostic.java'
include '**/com/android/tools/r8/DiagnosticsHandler.java'
include '**/com/android/tools/r8/DirectoryClassFileProvider.java'
include '**/com/android/tools/r8/OutputMode.java'
include '**/com/android/tools/r8/ProgramConsumer.java'
include '**/com/android/tools/r8/ProgramResource.java'
include '**/com/android/tools/r8/ProgramResourceProvider.java'
include '**/com/android/tools/r8/Resource.java'
include '**/com/android/tools/r8/ResourceException.java'
include '**/com/android/tools/r8/StringConsumer.java'
include '**/com/android/tools/r8/StringResource.java'
include '**/com/android/tools/r8/Version.java'
include '**/com/android/tools/r8/origin/*.java'
}
task javadocR8(type: Javadoc) {
title "R8 API"
classpath = sourceSets.main.compileClasspath
source = sourceSets.main.allJava
include '**/com/android/tools/r8/ArchiveClassFileProvider.java'
include '**/com/android/tools/r8/ArchiveProgramResourceProvider.java'
include '**/com/android/tools/r8/BaseCommand.java'
include '**/com/android/tools/r8/BaseCompilerCommand.java'
include '**/com/android/tools/r8/ClassFileConsumer.java'
include '**/com/android/tools/r8/ClassFileResourceProvider.java'
include '**/com/android/tools/r8/CompilationFailedException.java'
include '**/com/android/tools/r8/CompilationMode.java'
include '**/com/android/tools/r8/R8.java'
include '**/com/android/tools/r8/R8Command.java'
include '**/com/android/tools/r8/DexIndexedConsumer.java'
include '**/com/android/tools/r8/Diagnostic.java'
include '**/com/android/tools/r8/DiagnosticsHandler.java'
include '**/com/android/tools/r8/DirectoryClassFileProvider.java'
include '**/com/android/tools/r8/OutputMode.java'
include '**/com/android/tools/r8/ProgramConsumer.java'
include '**/com/android/tools/r8/ProgramResource.java'
include '**/com/android/tools/r8/ProgramResourceProvider.java'
include '**/com/android/tools/r8/Resource.java'
include '**/com/android/tools/r8/ResourceException.java'
include '**/com/android/tools/r8/StringConsumer.java'
include '**/com/android/tools/r8/StringResource.java'
include '**/com/android/tools/r8/Version.java'
include '**/com/android/tools/r8/origin/*.java'
}
task copyMavenDeps(type: Copy) {
from configurations.compile into "$buildDir/deps"
from configurations.compileClasspath into "$buildDir/deps"
from configurations.testCompile into "$buildDir/deps"
}
task printMavenDeps {
// Only actually print to stdout when we are updating.
if (project.hasProperty('updatemavendeps')) {
for (Configuration config : configurations) {
if (!config.isCanBeResolved()) {
continue
}
def componentIds = config.incoming.resolutionResult.allDependencies.collect {
it.selected.id
}
def result = dependencies.createArtifactResolutionQuery()
.forComponents(componentIds)
.withArtifacts(MavenModule, MavenPomArtifact)
.execute()
for (component in result.resolvedComponents) {
component.getArtifacts(MavenPomArtifact).each {
println "POM: ${it.file} ${component.id}"
}
}
config.each {
println "JAR: ${it}"
}
}
}
}
allprojects {
tasks.withType(Exec) {
doFirst {
println commandLine.join(' ')
}
}
}