Merge pull request #16947 from gradle/wolfs/tc/retry-flaky-test

Add a build to rerun flaky tests when desired
diff --git a/.teamcity/src/main/kotlin/common/Jvm.kt b/.teamcity/src/main/kotlin/common/Jvm.kt
new file mode 100644
index 0000000..a164df2
--- /dev/null
+++ b/.teamcity/src/main/kotlin/common/Jvm.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package common
+
+interface Jvm {
+    val version: JvmVersion
+    val vendor: JvmVendor
+}
+
+data class DefaultJvm(
+    override val version: JvmVersion,
+    override val vendor: JvmVendor
+) : Jvm
+
+object BuildToolBuildJvm : Jvm {
+    override val version: JvmVersion
+        get() = JvmVersion.java11
+    override val vendor: JvmVendor
+        get() = JvmVendor.openjdk
+}
diff --git a/.teamcity/src/main/kotlin/common/JvmCategory.kt b/.teamcity/src/main/kotlin/common/JvmCategory.kt
index 22fb8d2..1ed068c 100644
--- a/.teamcity/src/main/kotlin/common/JvmCategory.kt
+++ b/.teamcity/src/main/kotlin/common/JvmCategory.kt
@@ -16,7 +16,10 @@
 
 package common
 
-enum class JvmCategory(val vendor: JvmVendor, val version: JvmVersion) {
+enum class JvmCategory(
+    override val vendor: JvmVendor,
+    override val version: JvmVersion
+) : Jvm {
     MIN_VERSION(JvmVendor.oracle, JvmVersion.java8),
     MAX_VERSION(JvmVendor.oracle, JvmVersion.java16),
     EXPERIMENTAL_VERSION(JvmVendor.oracle, JvmVersion.java17)
diff --git a/.teamcity/src/main/kotlin/common/Os.kt b/.teamcity/src/main/kotlin/common/Os.kt
index 963ce2e..54d0c8b 100644
--- a/.teamcity/src/main/kotlin/common/Os.kt
+++ b/.teamcity/src/main/kotlin/common/Os.kt
@@ -66,18 +66,10 @@
     fun asName() = name.toLowerCase().capitalize()
 
     fun javaInstallationLocations(): String {
-        val paths = enumValues<JvmVersion>().map { version -> {
+        val paths = enumValues<JvmVersion>().joinToString(",") { version ->
             val vendor = if (version.major >= 11) JvmVendor.openjdk else JvmVendor.oracle
-            asPlaceholder(version, vendor)
-        }() }.joinToString(",")
+            javaHome(DefaultJvm(version, vendor), this)
+        }
         return """"-Porg.gradle.java.installations.paths=$paths""""
     }
-
-    fun javaHomeForGradle(): String {
-        return asPlaceholder(JvmVersion.java11, JvmVendor.openjdk)
-    }
-
-    fun asPlaceholder(jvmVersion: JvmVersion, vendor: JvmVendor): String {
-        return "%${name.toLowerCase()}.$jvmVersion.$vendor.64bit%"
-    }
 }
diff --git a/.teamcity/src/main/kotlin/common/extensions.kt b/.teamcity/src/main/kotlin/common/extensions.kt
index 45e53fd..24c899c 100644
--- a/.teamcity/src/main/kotlin/common/extensions.kt
+++ b/.teamcity/src/main/kotlin/common/extensions.kt
@@ -17,6 +17,8 @@
 package common
 
 import configurations.branchesFilterExcluding
+import configurations.buildScanCustomValue
+import configurations.buildScanTag
 import configurations.m2CleanScriptUnixLike
 import configurations.m2CleanScriptWindows
 import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId
@@ -66,7 +68,7 @@
 
 const val failedTestArtifactDestination = ".teamcity/gradle-logs"
 
-fun BuildType.applyDefaultSettings(os: Os = Os.LINUX, timeout: Int = 30, versionedSettingsBranch: String? = null) {
+fun BuildType.applyDefaultSettings(os: Os = Os.LINUX, buildJvm: Jvm = BuildToolBuildJvm, timeout: Int = 30) {
     artifactRules = """
         build/report-* => $failedTestArtifactDestination
         build/tmp/test files/** => $failedTestArtifactDestination/test-files
@@ -75,15 +77,12 @@
         build/reports/dependency-verification/** => dependency-verification-reports
     """.trimIndent()
 
+    paramsForBuildToolBuild(buildJvm, os)
+
     vcs {
         root(AbsoluteId("Gradle_Branches_GradlePersonalBranches"))
         checkoutMode = CheckoutMode.ON_AGENT
-
-        branchFilter = when (versionedSettingsBranch) {
-            "master" -> branchesFilterExcluding("release")
-            "release" -> branchesFilterExcluding("master")
-            else -> branchesFilterExcluding()
-        }
+        branchFilter = branchesFilterExcluding()
     }
 
     requirements {
@@ -105,11 +104,32 @@
             }
         }
     }
+}
 
-    if (os == Os.LINUX || os == Os.MACOS) {
-        params {
+fun javaHome(jvm: Jvm, os: Os) = "%${os.name.toLowerCase()}.${jvm.version}.${jvm.vendor}.64bit%"
+
+fun BuildType.paramsForBuildToolBuild(buildJvm: Jvm = BuildToolBuildJvm, os: Os) {
+    params {
+        param("env.BOT_TEAMCITY_GITHUB_TOKEN", "%github.bot-teamcity.token%")
+        param("env.GRADLE_CACHE_REMOTE_PASSWORD", "%gradle.cache.remote.password%")
+        param("env.GRADLE_CACHE_REMOTE_URL", "%gradle.cache.remote.url%")
+        param("env.GRADLE_CACHE_REMOTE_USERNAME", "%gradle.cache.remote.username%")
+
+        param("env.JAVA_HOME", javaHome(buildJvm, os))
+        param("env.GRADLE_OPTS", "-Xmx1536m -XX:MaxPermSize=384m")
+        param("env.ANDROID_HOME", os.androidHome)
+        param("env.ANDROID_SDK_ROOT", os.androidHome)
+        if (os == Os.MACOS) {
+            // Use fewer parallel forks on macOs, since the agents are not very powerful.
+            param("maxParallelForks", "2")
+        }
+        if (os == Os.LINUX || os == Os.MACOS) {
             param("env.LC_ALL", "en_US.UTF-8")
         }
+
+        if (os == Os.MACOS) {
+            param("env.REPO_MIRROR_URLS", "")
+        }
     }
 }
 
@@ -148,3 +168,39 @@
         artifactRules = "build-receipt.properties => incoming-distributions"
     }
 }
+
+fun functionalTestExtraParameters(buildScanTag: String, os: Os, testJvmVersion: String, testJvmVendor: String): String {
+    val buildScanValues = mapOf(
+        "coverageOs" to os.name.toLowerCase(),
+        "coverageJvmVendor" to testJvmVendor,
+        "coverageJvmVersion" to "java$testJvmVersion"
+    )
+    return (listOf(
+        "-PtestJavaVersion=$testJvmVersion",
+        "-PtestJavaVendor=$testJvmVendor") +
+        listOf(buildScanTag(buildScanTag)) +
+        buildScanValues.map { buildScanCustomValue(it.key, it.value) }
+        ).filter { it.isNotBlank() }.joinToString(separator = " ")
+}
+
+fun functionalTestParameters(os: Os): List<String> {
+    return listOf(
+        "-PteamCityBuildId=%teamcity.build.id%",
+        os.javaInstallationLocations(),
+        "-Porg.gradle.java.installations.auto-download=false"
+    )
+}
+
+fun BuildType.killProcessStep(stepName: String, daemon: Boolean, os: Os) {
+    steps {
+        gradleWrapper {
+            name = stepName
+            executionMode = BuildStep.ExecutionMode.ALWAYS
+            tasks = "killExistingProcessesStartedByGradle"
+            gradleParams = (
+                buildToolGradleParameters(daemon) +
+                    "-DpublishStrategy=publishOnFailure" // https://github.com/gradle/gradle-enterprise-conventions-plugin/pull/8
+                ).joinToString(separator = " ")
+        }
+    }
+}
diff --git a/.teamcity/src/main/kotlin/common/performance-test-extensions.kt b/.teamcity/src/main/kotlin/common/performance-test-extensions.kt
index e69c5b0..c24838c 100644
--- a/.teamcity/src/main/kotlin/common/performance-test-extensions.kt
+++ b/.teamcity/src/main/kotlin/common/performance-test-extensions.kt
@@ -32,8 +32,6 @@
         requiresNoEc2Agent()
     }
     params {
-        param("env.GRADLE_OPTS", "-Xmx1536m -XX:MaxPermSize=384m")
-        param("env.JAVA_HOME", os.javaHomeForGradle())
         param("env.BUILD_BRANCH", "%teamcity.build.branch%")
         param("env.JPROFILER_HOME", os.jprofilerHome)
         param("performance.db.username", "tcagent")
diff --git a/.teamcity/src/main/kotlin/configurations/BaseGradleBuildType.kt b/.teamcity/src/main/kotlin/configurations/BaseGradleBuildType.kt
index 5228b85..e139ada 100644
--- a/.teamcity/src/main/kotlin/configurations/BaseGradleBuildType.kt
+++ b/.teamcity/src/main/kotlin/configurations/BaseGradleBuildType.kt
@@ -1,17 +1,10 @@
 package configurations
 
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
-import model.CIBuildModel
 import model.Stage
 
-open class BaseGradleBuildType(model: CIBuildModel, val stage: Stage? = null, usesParentBuildCache: Boolean = false, init: BaseGradleBuildType.() -> Unit = {}) : BuildType() {
+open class BaseGradleBuildType(val stage: Stage? = null, init: BaseGradleBuildType.() -> Unit = {}) : BuildType() {
     init {
         this.init()
-        params {
-            param("env.BOT_TEAMCITY_GITHUB_TOKEN", "%github.bot-teamcity.token%")
-            param("env.GRADLE_CACHE_REMOTE_PASSWORD", "%gradle.cache.remote.password%")
-            param("env.GRADLE_CACHE_REMOTE_URL", "%gradle.cache.remote.url%")
-            param("env.GRADLE_CACHE_REMOTE_USERNAME", "%gradle.cache.remote.username%")
-        }
     }
 }
diff --git a/.teamcity/src/main/kotlin/configurations/BuildDistributions.kt b/.teamcity/src/main/kotlin/configurations/BuildDistributions.kt
index 5a04d2b..aab1fcf 100644
--- a/.teamcity/src/main/kotlin/configurations/BuildDistributions.kt
+++ b/.teamcity/src/main/kotlin/configurations/BuildDistributions.kt
@@ -4,7 +4,7 @@
 import model.CIBuildModel
 import model.Stage
 
-class BuildDistributions(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(model, stage = stage, init = {
+class BuildDistributions(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(stage = stage, init = {
     id("${model.projectId}_BuildDistributions")
     name = "Build Distributions"
     description = "Creation and verification of the distribution and documentation"
@@ -26,8 +26,4 @@
         subprojects/distributions-full/build/distributions/*.zip => distributions
         subprojects/base-services/build/generated-resources/build-receipt/org/gradle/build-receipt.properties
     """.trimIndent()
-
-    params {
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
 })
diff --git a/.teamcity/src/main/kotlin/configurations/CompileAll.kt b/.teamcity/src/main/kotlin/configurations/CompileAll.kt
index 17aac5d..f50c1d9 100644
--- a/.teamcity/src/main/kotlin/configurations/CompileAll.kt
+++ b/.teamcity/src/main/kotlin/configurations/CompileAll.kt
@@ -1,18 +1,13 @@
 package configurations
 
-import common.Os.LINUX
 import model.CIBuildModel
 import model.Stage
 
-class CompileAll(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(model, stage = stage, usesParentBuildCache = true, init = {
+class CompileAll(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(stage = stage, init = {
     id(buildTypeId(model))
     name = "Compile All"
     description = "Compiles all the source code and warms up the build cache"
 
-    params {
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
-
     features {
         publishBuildStatusToGithub(model)
     }
@@ -29,6 +24,7 @@
     """.trimIndent()
 }) {
     companion object {
-        fun buildTypeId(model: CIBuildModel) = "${model.projectId}_CompileAllBuild"
+        fun buildTypeId(model: CIBuildModel) = buildTypeId(model.projectId)
+        fun buildTypeId(projectId: String) = "${projectId}_CompileAllBuild"
     }
 }
diff --git a/.teamcity/src/main/kotlin/configurations/FunctionalTest.kt b/.teamcity/src/main/kotlin/configurations/FunctionalTest.kt
index 57b4473..838f4c0 100644
--- a/.teamcity/src/main/kotlin/configurations/FunctionalTest.kt
+++ b/.teamcity/src/main/kotlin/configurations/FunctionalTest.kt
@@ -1,6 +1,7 @@
 package configurations
 
 import common.Os
+import common.functionalTestExtraParameters
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildSteps
 import model.CIBuildModel
 import model.Stage
@@ -18,17 +19,11 @@
     extraParameters: String = "",
     extraBuildSteps: BuildSteps.() -> Unit = {},
     preBuildSteps: BuildSteps.() -> Unit = {}
-) : BaseGradleBuildType(model, stage = stage, init = {
+) : BaseGradleBuildType(stage = stage, init = {
     this.name = name
     this.description = description
     this.id(id)
     val testTasks = getTestTaskName(testCoverage, subprojects)
-    val buildScanTags = listOf("FunctionalTest")
-    val buildScanValues = mapOf(
-        "coverageOs" to testCoverage.os.name.toLowerCase(),
-        "coverageJvmVendor" to testCoverage.vendor.name,
-        "coverageJvmVersion" to testCoverage.testJvmVersion.name
-    )
 
     if (name.contains("(configuration-cache)")) {
         requirements {
@@ -42,11 +37,7 @@
 
     applyTestDefaults(model, this, testTasks, notQuick = !testCoverage.isQuick, os = testCoverage.os,
         extraParameters = (
-            listOf(
-                "-PtestJavaVersion=${testCoverage.testJvmVersion.major}",
-                "-PtestJavaVendor=${testCoverage.vendor.name}") +
-                buildScanTags.map { buildScanTag(it) } +
-                buildScanValues.map { buildScanCustomValue(it.key, it.value) } +
+            listOf(functionalTestExtraParameters("FunctionalTest", testCoverage.os, testCoverage.testJvmVersion.major.toString(), testCoverage.vendor.name)) +
                 if (enableExperimentalTestDistribution(testCoverage, subprojects)) "-DenableTestDistribution=%enableTestDistribution%" else "" +
                     extraParameters
             ).filter { it.isNotBlank() }.joinToString(separator = " "),
@@ -59,14 +50,6 @@
             param("env.GRADLE_ENTERPRISE_ACCESS_KEY", "%e.grdev.net.access.key%")
         }
 
-        param("env.JAVA_HOME", "%${testCoverage.os.name.toLowerCase()}.${testCoverage.buildJvmVersion}.openjdk.64bit%")
-        param("env.ANDROID_HOME", testCoverage.os.androidHome)
-        param("env.ANDROID_SDK_ROOT", testCoverage.os.androidHome)
-        if (testCoverage.os == Os.MACOS) {
-            // Use fewer parallel forks on macOs, since the agents are not very powerful.
-            param("maxParallelForks", "2")
-        }
-
         if (testCoverage.testDistribution) {
             param("maxParallelForks", "16")
         }
diff --git a/.teamcity/src/main/kotlin/configurations/FunctionalTestsPass.kt b/.teamcity/src/main/kotlin/configurations/FunctionalTestsPass.kt
index 9a3665a..7cecdcd 100644
--- a/.teamcity/src/main/kotlin/configurations/FunctionalTestsPass.kt
+++ b/.teamcity/src/main/kotlin/configurations/FunctionalTestsPass.kt
@@ -20,7 +20,7 @@
 import model.CIBuildModel
 import projects.FunctionalTestProject
 
-class FunctionalTestsPass(model: CIBuildModel, functionalTestProject: FunctionalTestProject) : BaseGradleBuildType(model, init = {
+class FunctionalTestsPass(model: CIBuildModel, functionalTestProject: FunctionalTestProject) : BaseGradleBuildType(init = {
     id("${functionalTestProject.testConfig.asId(model)}_Trigger")
     name = functionalTestProject.name + " (Trigger)"
 
diff --git a/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt b/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
index a2877bb..df6fbcc 100644
--- a/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
+++ b/.teamcity/src/main/kotlin/configurations/GradleBuildConfigurationDefaults.kt
@@ -5,7 +5,9 @@
 import common.buildToolGradleParameters
 import common.checkCleanM2
 import common.compileAllDependency
+import common.functionalTestParameters
 import common.gradleWrapper
+import common.killProcessStep
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildFeatures
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildSteps
@@ -82,31 +84,23 @@
 
 fun BaseGradleBuildType.gradleRunnerStep(model: CIBuildModel, gradleTasks: String, os: Os = Os.LINUX, extraParameters: String = "", daemon: Boolean = true) {
     val buildScanTags = model.buildScanTags + listOfNotNull(stage?.id)
+    val parameters = (
+        buildToolGradleParameters(daemon) +
+            listOf(extraParameters) +
+            buildScanTags.map { buildScanTag(it) } +
+            functionalTestParameters(os)
+        ).joinToString(separator = " ")
 
     steps {
         gradleWrapper {
             name = "SHOW_TOOLCHAINS"
             tasks = "javaToolchains"
-            gradleParams = (
-                buildToolGradleParameters(daemon) +
-                    listOf(extraParameters) +
-                    "-PteamCityBuildId=%teamcity.build.id%" +
-                    buildScanTags.map { buildScanTag(it) } +
-                    os.javaInstallationLocations() +
-                    "-Porg.gradle.java.installations.auto-download=false"
-                ).joinToString(separator = " ")
+            gradleParams = parameters
         }
         gradleWrapper {
             name = "GRADLE_RUNNER"
             tasks = "clean $gradleTasks"
-            gradleParams = (
-                    buildToolGradleParameters(daemon) +
-                    listOf(extraParameters) +
-                    "-PteamCityBuildId=%teamcity.build.id%" +
-                    buildScanTags.map { buildScanTag(it) } +
-                    os.javaInstallationLocations() +
-                    "-Porg.gradle.java.installations.auto-download=false"
-                ).joinToString(separator = " ")
+            gradleParams = parameters
         }
     }
 }
@@ -145,23 +139,8 @@
     }
 }
 
-private
-fun BaseGradleBuildType.killProcessStep(stepName: String, daemon: Boolean, os: Os) {
-    steps {
-        gradleWrapper {
-            name = stepName
-            executionMode = BuildStep.ExecutionMode.ALWAYS
-            tasks = "killExistingProcessesStartedByGradle"
-            gradleParams = (
-                buildToolGradleParameters(daemon) +
-                    "-DpublishStrategy=publishOnFailure" // https://github.com/gradle/gradle-enterprise-conventions-plugin/pull/8
-                ).joinToString(separator = " ")
-        }
-    }
-}
-
 fun applyDefaults(model: CIBuildModel, buildType: BaseGradleBuildType, gradleTasks: String, notQuick: Boolean = false, os: Os = Os.LINUX, extraParameters: String = "", timeout: Int = 90, extraSteps: BuildSteps.() -> Unit = {}, daemon: Boolean = true) {
-    buildType.applyDefaultSettings(os, timeout)
+    buildType.applyDefaultSettings(os, timeout = timeout)
 
     buildType.killProcessStep("KILL_LEAKED_PROCESSES_FROM_PREVIOUS_BUILDS", daemon, os)
     buildType.gradleRunnerStep(model, gradleTasks, os, extraParameters, daemon)
@@ -190,7 +169,7 @@
         buildType.params.param("env.REPO_MIRROR_URLS", "")
     }
 
-    buildType.applyDefaultSettings(os, timeout)
+    buildType.applyDefaultSettings(os, timeout = timeout)
 
     buildType.steps {
         preSteps()
diff --git a/.teamcity/src/main/kotlin/configurations/Gradleception.kt b/.teamcity/src/main/kotlin/configurations/Gradleception.kt
index 6939095..4036a14 100644
--- a/.teamcity/src/main/kotlin/configurations/Gradleception.kt
+++ b/.teamcity/src/main/kotlin/configurations/Gradleception.kt
@@ -1,6 +1,5 @@
 package configurations
 
-import common.Os.LINUX
 import common.buildToolGradleParameters
 import common.customGradle
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildSteps
@@ -8,15 +7,11 @@
 import model.CIBuildModel
 import model.Stage
 
-class Gradleception(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(model, stage = stage, init = {
+class Gradleception(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(stage = stage, init = {
     id("${model.projectId}_Gradleception")
     name = "Gradleception - Java8 Linux"
     description = "Builds Gradle with the version of Gradle which is currently under development (twice)"
 
-    params {
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
-
     features {
         publishBuildStatusToGithub(model)
     }
diff --git a/.teamcity/src/main/kotlin/configurations/PartialTrigger.kt b/.teamcity/src/main/kotlin/configurations/PartialTrigger.kt
index 4e37371..3b208cc 100644
--- a/.teamcity/src/main/kotlin/configurations/PartialTrigger.kt
+++ b/.teamcity/src/main/kotlin/configurations/PartialTrigger.kt
@@ -20,7 +20,7 @@
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
 import model.CIBuildModel
 
-class PartialTrigger<T : BuildType>(triggerName: String, triggerId: String, model: CIBuildModel, dependencies: Iterable<T>) : BaseGradleBuildType(model, init = {
+class PartialTrigger<T : BuildType>(triggerName: String, triggerId: String, model: CIBuildModel, dependencies: Iterable<T>) : BaseGradleBuildType(init = {
     id("${model.projectId}_${triggerId}_Trigger")
     name = "$triggerName (Trigger)"
     type = Type.COMPOSITE
diff --git a/.teamcity/src/main/kotlin/configurations/PerformanceTest.kt b/.teamcity/src/main/kotlin/configurations/PerformanceTest.kt
index 8d7b29e..a1b6ae2 100644
--- a/.teamcity/src/main/kotlin/configurations/PerformanceTest.kt
+++ b/.teamcity/src/main/kotlin/configurations/PerformanceTest.kt
@@ -43,7 +43,6 @@
     performanceTestTaskSuffix: String = "PerformanceTest",
     preBuildSteps: BuildSteps.() -> Unit = {}
 ) : BaseGradleBuildType(
-    model,
     stage = stage,
     init = {
         this.id(performanceTestBuildSpec.asConfigurationId(model, "bucket${bucketIndex + 1}"))
@@ -57,8 +56,6 @@
         params {
             param("performance.baselines", type.defaultBaselines)
             param("performance.channel", performanceTestBuildSpec.channel())
-            param("env.ANDROID_HOME", os.androidHome)
-            param("env.ANDROID_SDK_ROOT", os.androidHome)
             param("env.PERFORMANCE_DB_PASSWORD_TCAGENT", "%performance.db.password.tcagent%")
             when (os) {
                 Os.WINDOWS -> param("env.PATH", "%env.PATH%;C:/Program Files/7-zip")
diff --git a/.teamcity/src/main/kotlin/configurations/PerformanceTestsPass.kt b/.teamcity/src/main/kotlin/configurations/PerformanceTestsPass.kt
index 1c099b4..5d461a8 100644
--- a/.teamcity/src/main/kotlin/configurations/PerformanceTestsPass.kt
+++ b/.teamcity/src/main/kotlin/configurations/PerformanceTestsPass.kt
@@ -24,7 +24,7 @@
 import model.PerformanceTestType
 import projects.PerformanceTestProject
 
-class PerformanceTestsPass(model: CIBuildModel, performanceTestProject: PerformanceTestProject) : BaseGradleBuildType(model, init = {
+class PerformanceTestsPass(model: CIBuildModel, performanceTestProject: PerformanceTestProject) : BaseGradleBuildType(init = {
     id("${performanceTestProject.spec.asConfigurationId(model)}_Trigger")
     val performanceTestSpec = performanceTestProject.spec
     name = performanceTestProject.name + " (Trigger)"
@@ -34,8 +34,6 @@
 
     applyDefaultSettings(os)
     params {
-        param("env.GRADLE_OPTS", "-Xmx1536m -XX:MaxPermSize=384m")
-        param("env.JAVA_HOME", os.javaHomeForGradle())
         param("env.BUILD_BRANCH", "%teamcity.build.branch%")
         param("env.PERFORMANCE_DB_PASSWORD_TCAGENT", "%performance.db.password.tcagent%")
         param("performance.db.username", "tcagent")
diff --git a/.teamcity/src/main/kotlin/configurations/SanityCheck.kt b/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
index 48970ce..6dd855e 100644
--- a/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
+++ b/.teamcity/src/main/kotlin/configurations/SanityCheck.kt
@@ -1,18 +1,13 @@
 package configurations
 
-import common.Os.LINUX
 import model.CIBuildModel
 import model.Stage
 
-class SanityCheck(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(model, stage = stage, usesParentBuildCache = true, init = {
+class SanityCheck(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(stage = stage, init = {
     id(buildTypeId(model))
     name = "Sanity Check"
     description = "Static code analysis, checkstyle, release notes verification, etc."
 
-    params {
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
-
     features {
         publishBuildStatusToGithub(model)
         triggeredOnPullRequests()
diff --git a/.teamcity/src/main/kotlin/configurations/SmokeTests.kt b/.teamcity/src/main/kotlin/configurations/SmokeTests.kt
index 62e142c..a0e9865 100644
--- a/.teamcity/src/main/kotlin/configurations/SmokeTests.kt
+++ b/.teamcity/src/main/kotlin/configurations/SmokeTests.kt
@@ -1,21 +1,14 @@
 package configurations
 
 import common.JvmCategory
-import common.Os.LINUX
 import model.CIBuildModel
 import model.Stage
 
-class SmokeTests(model: CIBuildModel, stage: Stage, testJava: JvmCategory, task: String = "smokeTest") : BaseGradleBuildType(model, stage = stage, init = {
+class SmokeTests(model: CIBuildModel, stage: Stage, testJava: JvmCategory, task: String = "smokeTest") : BaseGradleBuildType(stage = stage, init = {
     id("${model.projectId}_${task.capitalize()}s${testJava.version.name.capitalize()}")
     name = "Smoke Tests with 3rd Party Plugins ($task) - ${testJava.version.name.capitalize()} Linux"
     description = "Smoke tests against third party plugins to see if they still work with the current Gradle version"
 
-    params {
-        param("env.ANDROID_HOME", LINUX.androidHome)
-        param("env.ANDROID_SDK_ROOT", LINUX.androidHome)
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
-
     features {
         publishBuildStatusToGithub(model)
     }
diff --git a/.teamcity/src/main/kotlin/configurations/StagePasses.kt b/.teamcity/src/main/kotlin/configurations/StagePasses.kt
index 01eafb7..a02cc9a 100644
--- a/.teamcity/src/main/kotlin/configurations/StagePasses.kt
+++ b/.teamcity/src/main/kotlin/configurations/StagePasses.kt
@@ -1,6 +1,5 @@
 package configurations
 
-import common.Os.LINUX
 import common.applyDefaultSettings
 import common.buildToolGradleParameters
 import common.gradleWrapper
@@ -22,7 +21,7 @@
 import model.Trigger
 import projects.StageProject
 
-class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, stageProject: StageProject) : BaseGradleBuildType(model, init = {
+class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, stageProject: StageProject) : BaseGradleBuildType(init = {
     id(stageTriggerId(model, stage))
     name = stage.stageName.stageName + " (Trigger)"
 
@@ -60,11 +59,6 @@
         }
     }
 
-    params {
-        param("env.JAVA_HOME", LINUX.javaHomeForGradle())
-    }
-
-    val baseBuildType = this
     val buildScanTags = model.buildScanTags + stage.id
 
     val defaultGradleParameters = (
diff --git a/.teamcity/src/main/kotlin/configurations/TestPerformanceTest.kt b/.teamcity/src/main/kotlin/configurations/TestPerformanceTest.kt
index 21796aa..e55cb45 100644
--- a/.teamcity/src/main/kotlin/configurations/TestPerformanceTest.kt
+++ b/.teamcity/src/main/kotlin/configurations/TestPerformanceTest.kt
@@ -28,7 +28,7 @@
 import model.CIBuildModel
 import model.Stage
 
-class TestPerformanceTest(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(model, stage, init = {
+class TestPerformanceTest(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(stage, init = {
     val os = Os.LINUX
     val testProject = "smallJavaMultiProject"
 
diff --git a/.teamcity/src/main/kotlin/promotion/BasePromotionBuildType.kt b/.teamcity/src/main/kotlin/promotion/BasePromotionBuildType.kt
index b029e00..2cb525e 100644
--- a/.teamcity/src/main/kotlin/promotion/BasePromotionBuildType.kt
+++ b/.teamcity/src/main/kotlin/promotion/BasePromotionBuildType.kt
@@ -16,7 +16,9 @@
 
 package promotion
 
+import common.BuildToolBuildJvm
 import common.Os
+import common.paramsForBuildToolBuild
 import common.requiresNoEc2Agent
 import common.requiresOs
 import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId
@@ -38,11 +40,10 @@
             requiresNoEc2Agent()
         }
 
+        paramsForBuildToolBuild(BuildToolBuildJvm, Os.LINUX)
+
         params {
             param("env.GE_GRADLE_ORG_GRADLE_ENTERPRISE_ACCESS_KEY", "%ge.gradle.org.access.key%")
-            param("env.GRADLE_CACHE_REMOTE_PASSWORD", "%gradle.cache.remote.password%")
-            param("env.GRADLE_CACHE_REMOTE_URL", "%gradle.cache.remote.url%")
-            param("env.GRADLE_CACHE_REMOTE_USERNAME", "%gradle.cache.remote.username%")
         }
 
         features {
diff --git a/.teamcity/src/main/kotlin/promotion/PromotionProject.kt b/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
index c4d9120..56f51e1 100644
--- a/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
+++ b/.teamcity/src/main/kotlin/promotion/PromotionProject.kt
@@ -1,6 +1,9 @@
 package promotion
 
+import common.BuildToolBuildJvm
+import common.Os
 import common.VersionedSettingsBranch
+import common.javaHome
 import jetbrains.buildServer.configs.kotlin.v2019_2.Project
 
 class PromotionProject(branch: VersionedSettingsBranch) : Project({
@@ -28,7 +31,7 @@
         password("env.DOTCOM_DEV_DOCS_AWS_SECRET_KEY", "%dotcomDevDocsAwsSecretKey%")
         param("env.DOTCOM_DEV_DOCS_AWS_ACCESS_KEY", "AKIAX5VJCER2X7DPYFXF")
         password("env.ORG_GRADLE_PROJECT_sdkmanToken", "%sdkmanToken%")
-        param("env.JAVA_HOME", "%linux.java11.openjdk.64bit%")
+        param("env.JAVA_HOME", javaHome(BuildToolBuildJvm, Os.LINUX))
         param("env.ORG_GRADLE_PROJECT_artifactoryUserName", "bot-build-tool")
         password("env.ORG_GRADLE_PROJECT_infrastructureEmailPwd", "%infrastructureEmailPwd%")
         param("env.ORG_GRADLE_PROJECT_sdkmanKey", "8ed1a771bc236c287ad93c699bfdd2d7")
diff --git a/.teamcity/src/main/kotlin/util/AdHocPerformanceScenario.kt b/.teamcity/src/main/kotlin/util/AdHocPerformanceScenario.kt
index 674484a..b2370c5 100644
--- a/.teamcity/src/main/kotlin/util/AdHocPerformanceScenario.kt
+++ b/.teamcity/src/main/kotlin/util/AdHocPerformanceScenario.kt
@@ -63,8 +63,6 @@
 
         param("env.PERFORMANCE_DB_PASSWORD_TCAGENT", "%performance.db.password.tcagent%")
         param("additional.gradle.parameters", "")
-        param("env.ANDROID_HOME", os.androidHome)
-        param("env.ANDROID_SDK_ROOT", os.androidHome)
     }
 
     steps {
diff --git a/.teamcity/src/main/kotlin/util/RerunFlakyTest.kt b/.teamcity/src/main/kotlin/util/RerunFlakyTest.kt
new file mode 100644
index 0000000..e84e19e
--- /dev/null
+++ b/.teamcity/src/main/kotlin/util/RerunFlakyTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import common.BuildToolBuildJvm
+import common.Os
+import common.applyDefaultSettings
+import common.buildToolGradleParameters
+import common.checkCleanM2
+import common.compileAllDependency
+import common.functionalTestExtraParameters
+import common.functionalTestParameters
+import common.gradleWrapper
+import common.killProcessStep
+import configurations.CompileAll
+import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep
+import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
+import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
+
+class RerunFlakyTest(os: Os) : BuildType({
+    val id = "Util_RerunFlakyTest${os.asName()}"
+    name = "Rerun Flaky Test - ${os.asName()}"
+    description = "Allows you to rerun a selected flaky test 10 times"
+    id(id)
+    val testJvmVendorParameter = "testJavaVendor"
+    val testJvmVersionParameter = "testJavaVersion"
+    val testTaskParameterName = "testTask"
+    val testTaskOptionsParameterName = "testTaskOptions"
+    val daemon = true
+    applyDefaultSettings(os, BuildToolBuildJvm, 30)
+    val extraParameters = functionalTestExtraParameters("RerunFlakyTest", os, "%$testJvmVersionParameter%", "%$testJvmVendorParameter%")
+    val parameters = (
+        buildToolGradleParameters(daemon) +
+            listOf(extraParameters) +
+            functionalTestParameters(os)
+        ).joinToString(separator = " ")
+
+    killProcessStep("KILL_LEAKED_PROCESSES_FROM_PREVIOUS_BUILDS", daemon, os)
+    steps {
+        gradleWrapper {
+            name = "SHOW_TOOLCHAINS"
+            tasks = "javaToolchains"
+            gradleParams = parameters
+        }
+    }
+    (1..10).forEach { idx ->
+        steps {
+            gradleWrapper {
+                name = "GRADLE_RUNNER_$idx"
+                tasks = "%$testTaskParameterName% --rerun %$testTaskOptionsParameterName%"
+                gradleParams = parameters
+                executionMode = BuildStep.ExecutionMode.ALWAYS
+            }
+        }
+        killProcessStep("KILL_PROCESSES_STARTED_BY_GRADLE", daemon, os)
+    }
+    steps {
+        checkCleanM2(os)
+    }
+
+    params {
+        text(
+            testTaskParameterName,
+            ":core:embeddedIntegTest",
+            display = ParameterDisplay.PROMPT,
+            allowEmpty = false,
+            description = "The test task you want to run"
+        )
+        text(
+            testTaskOptionsParameterName,
+            """--tests "org.gradle.api.tasks.CachedTaskExecutionIntegrationTest.outputs are correctly loaded from cache"""",
+            display = ParameterDisplay.PROMPT,
+            allowEmpty = false,
+            description = "Options for the test task to run, like e.g. the test filter"
+        )
+        text(
+            testJvmVersionParameter,
+            "11",
+            display = ParameterDisplay.PROMPT,
+            allowEmpty = false,
+            description = "Java version to run the test with"
+        )
+        text(
+            testJvmVendorParameter,
+            "openjdk",
+            display = ParameterDisplay.PROMPT,
+            allowEmpty = false,
+            description = "Java vendor to run the test with"
+        )
+    }
+
+    dependencies {
+        compileAllDependency(CompileAll.buildTypeId("Check"))
+    }
+})
diff --git a/.teamcity/src/main/kotlin/util/UtilProject.kt b/.teamcity/src/main/kotlin/util/UtilProject.kt
index 6f6ddac..8ce217c 100644
--- a/.teamcity/src/main/kotlin/util/UtilProject.kt
+++ b/.teamcity/src/main/kotlin/util/UtilProject.kt
@@ -1,10 +1,14 @@
 package util
 
+import common.Os
 import jetbrains.buildServer.configs.kotlin.v2019_2.Project
 
 object UtilProject : Project({
     id("Util")
     name = "Util"
 
+    buildType(RerunFlakyTest(Os.LINUX))
+    buildType(RerunFlakyTest(Os.WINDOWS))
+    buildType(RerunFlakyTest(Os.MACOS))
     buildType(WarmupEc2Agent)
 })
diff --git a/.teamcity/src/main/kotlin/util/WarmupEc2Agent.kt b/.teamcity/src/main/kotlin/util/WarmupEc2Agent.kt
index 4cbdd93..5fb23e5 100644
--- a/.teamcity/src/main/kotlin/util/WarmupEc2Agent.kt
+++ b/.teamcity/src/main/kotlin/util/WarmupEc2Agent.kt
@@ -1,8 +1,10 @@
 package util
 
+import common.BuildToolBuildJvm
 import common.Os
 import common.buildToolGradleParameters
 import common.gradleWrapper
+import common.javaHome
 import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
 import jetbrains.buildServer.configs.kotlin.v2019_2.Requirement
 import jetbrains.buildServer.configs.kotlin.v2019_2.RequirementType
@@ -25,7 +27,7 @@
 
     params {
         param("defaultBranchName", "master")
-        param("env.JAVA_HOME", Os.LINUX.javaHomeForGradle())
+        param("env.JAVA_HOME", javaHome(BuildToolBuildJvm, Os.LINUX))
     }
 
     steps {
diff --git a/.teamcity/src/test/kotlin/ApplyDefaultConfigurationTest.kt b/.teamcity/src/test/kotlin/ApplyDefaultConfigurationTest.kt
index c62276d..e0dece4 100644
--- a/.teamcity/src/test/kotlin/ApplyDefaultConfigurationTest.kt
+++ b/.teamcity/src/test/kotlin/ApplyDefaultConfigurationTest.kt
@@ -136,6 +136,6 @@
         val linuxPaths = "-Porg.gradle.java.installations.paths=%linux.java8.oracle.64bit%,%linux.java9.oracle.64bit%,%linux.java10.oracle.64bit%,%linux.java11.openjdk.64bit%,%linux.java12.openjdk.64bit%,%linux.java13.openjdk.64bit%,%linux.java14.openjdk.64bit%,%linux.java15.openjdk.64bit%,%linux.java16.openjdk.64bit%,%linux.java17.openjdk.64bit%"
         val windowsPaths = "-Porg.gradle.java.installations.paths=%windows.java8.oracle.64bit%,%windows.java9.oracle.64bit%,%windows.java10.oracle.64bit%,%windows.java11.openjdk.64bit%,%windows.java12.openjdk.64bit%,%windows.java13.openjdk.64bit%,%windows.java14.openjdk.64bit%,%windows.java15.openjdk.64bit%,%windows.java16.openjdk.64bit%,%windows.java17.openjdk.64bit%"
         val expectedInstallationPaths = if (os == Os.WINDOWS) windowsPaths else linuxPaths
-        return "-Dorg.gradle.workers.max=%maxParallelForks% -PmaxParallelForks=%maxParallelForks% -s $daemon --continue $extraParameters -PteamCityBuildId=%teamcity.build.id% \"-Dscan.tag.Check\" \"-Dscan.tag.\" \"$expectedInstallationPaths\" -Porg.gradle.java.installations.auto-download=false"
+        return "-Dorg.gradle.workers.max=%maxParallelForks% -PmaxParallelForks=%maxParallelForks% -s $daemon --continue $extraParameters \"-Dscan.tag.Check\" \"-Dscan.tag.\" -PteamCityBuildId=%teamcity.build.id% \"$expectedInstallationPaths\" -Porg.gradle.java.installations.auto-download=false"
     }
 }