diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 50a00c387b2..29a852f9ad9 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1059,6 +1059,12 @@ + + + + + + @@ -1305,6 +1311,18 @@ + + + + + + + + + + + + @@ -1779,6 +1797,12 @@ + + + + + + @@ -2275,6 +2299,24 @@ + + + + + + + + + + + + + + + + + + @@ -3830,6 +3872,12 @@ + + + + + + @@ -5286,6 +5334,12 @@ + + + + + + @@ -6151,6 +6205,12 @@ + + + + + + @@ -6360,6 +6420,12 @@ + + + + + + @@ -7579,6 +7645,12 @@ + + + + + + @@ -7615,6 +7687,12 @@ + + + + + + diff --git a/libraries/tools/gradle/regression-benchmark-templates/Readme.md b/libraries/tools/gradle/regression-benchmark-templates/Readme.md new file mode 100644 index 00000000000..cb40e1d58cd --- /dev/null +++ b/libraries/tools/gradle/regression-benchmark-templates/Readme.md @@ -0,0 +1,18 @@ +## Description + +Provides Kotlin script template to define and run [gradle-profiler](https://github.com/gradle/gradle-profiler) benchmarks. + +### Writing benchmark script + +This template is automatically applied to all script files with `.benchmark.kts` file extension. + +The script itself should have following: +- A `@file:BenchmarkProject` annotation providing name of the project-under-benchmark, +git url to the project repo and git commit sha which should be used to run benchmarks. +- definition of gradle-profiler scenarios is written in `suite { .. }` DSL. +- provide optional git patch files that will be applied to the project before running benchmarks. + +To run the actual benchmarks script either could call specific steps or just call `runAllBenchmarks()` function. +This function calls required steps in order, runs provided benchmarks and then shows aggregated result. + +Aggregated results are available in form of CSV or HTML files. diff --git a/libraries/tools/gradle/regression-benchmark-templates/build.gradle.kts b/libraries/tools/gradle/regression-benchmark-templates/build.gradle.kts new file mode 100644 index 00000000000..d8aa0acf39a --- /dev/null +++ b/libraries/tools/gradle/regression-benchmark-templates/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("org.jetbrains.kotlin.jvm") +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-scripting-common") + implementation("com.squareup.okhttp3:okhttp:4.9.0") + implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.0.202109080827-r") + implementation("org.slf4j:slf4j-nop:1.7.32") + implementation("org.jetbrains.kotlinx:dataframe:0.8.0-rc-3") +} diff --git a/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkScenario.kt b/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkScenario.kt new file mode 100644 index 00000000000..843f18a79ed --- /dev/null +++ b/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkScenario.kt @@ -0,0 +1,58 @@ +@file:Suppress("Unused", "Unused_Variable") +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.benchmark + +class Scenario { + lateinit var title: String + var warmups = 3 + var iterations = 10 + val tasks = mutableListOf() + val gradleArgs = mutableListOf() + val cleanupTasks = mutableListOf() + val applyAbiChange = mutableListOf() + val applyNonAbiChange = mutableListOf() + + fun runTasks(vararg tasks: String) { + this.tasks.addAll(tasks) + } + + fun useGradleArgs(vararg args: String) { + gradleArgs.addAll(args) + } + + fun runCleanupTasks(vararg tasks: String) { + cleanupTasks.addAll(tasks) + } + + fun applyAbiChangeTo(pathToClassFile: String) { + applyAbiChange.add(pathToClassFile) + } + + /** + * Currently not-working: https://github.com/gradle/gradle-profiler/issues/378 + */ + fun applyNonAbiChangeTo(pathToClassFile: String) { + applyNonAbiChange.add(pathToClassFile) + } +} + +class ScenarioSuite { + private val _scenarios = mutableListOf() + val scenarios: List get() = _scenarios.toList() + + fun scenario(init: Scenario.() -> Unit) { + val scenario = Scenario() + scenario.init() + _scenarios.add(scenario) + } +} + +fun suite(init: ScenarioSuite.() -> Unit): ScenarioSuite { + val suite = ScenarioSuite() + suite.init() + return suite +} diff --git a/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkTemplate.kt b/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkTemplate.kt new file mode 100644 index 00000000000..02b555630bf --- /dev/null +++ b/libraries/tools/gradle/regression-benchmark-templates/src/main/kotlin/org/jetbrains/kotlin/gradle/benchmark/BenchmarkTemplate.kt @@ -0,0 +1,433 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.benchmark + +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.IOException +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ResetCommand +import org.eclipse.jgit.lib.TextProgressMonitor +import org.jetbrains.kotlinx.dataframe.* +import org.jetbrains.kotlinx.dataframe.api.* +import org.jetbrains.kotlinx.dataframe.io.readCSV +import org.jetbrains.kotlinx.dataframe.io.toHTML +import org.jetbrains.kotlinx.dataframe.io.writeCSV +import org.jetbrains.kotlinx.dataframe.math.median +import java.io.File +import java.util.zip.ZipInputStream +import kotlin.reflect.typeOf +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.* +import kotlin.script.experimental.util.PropertiesCollection + +@Suppress("unused") +@KotlinScript( + fileExtension = "benchmark.kts", + compilationConfiguration = BenchmarkScriptDefinition::class, + evaluationConfiguration = BenchmarkEvaluationConfiguration::class +) +abstract class BenchmarkTemplate( + vararg args: String, + private val projectName: String, + private val projectGitUrl: String, + private val gitCommitSha: String, +) { + private val workingDir = File(args.first()) + private val gradleProfilerDir = workingDir.resolve("gradle-profiler") + private val projectRepoDir = workingDir.resolve(projectName) + private val scenariosDir = workingDir.resolve("scenarios") + private val benchmarkOutputsDir = workingDir.resolve("outputs") + + private val gitOperationsPrinter = TextProgressMonitor() + + fun runAllBenchmarks( + suite: ScenarioSuite, + benchmarks: Map + ) { + printStartingMessage() + downloadGradleProfilerIfNotAvailable() + checkoutRepository() + + val results = benchmarks.map { (name, patchFile) -> + repoReset() + patchFile?.let { repoApplyPatch(it) } + runBenchmark(suite, name) + } + aggregateBenchmarkResults(*results.toTypedArray()) + + printEndingMessage() + } + + fun printStartingMessage() { + println("$STEP_SEPARATOR Starting Gradle regression benchmark for $projectName $STEP_SEPARATOR") + } + + fun printEndingMessage() { + println("$STEP_SEPARATOR Gradle regression benchmark for $projectName has ended $STEP_SEPARATOR") + } + + fun downloadGradleProfilerIfNotAvailable() { + if (gradleProfilerDir.exists()) { + println("Gradle profiler has been already downloaded") + } else { + downloadAndExtractGradleProfiler() + } + } + + fun checkoutRepository(): File { + val git = if (projectRepoDir.exists()) { + println("Repository is available, resetting it state") + Git.open(projectRepoDir).also { + it.reset() + .setMode(ResetCommand.ResetType.HARD) + .setProgressMonitor(gitOperationsPrinter) + .call() + } + } else { + println("Running git checkout for $projectGitUrl") + projectRepoDir.mkdirs() + Git.cloneRepository() + .setDirectory(projectRepoDir) + .setCloneSubmodules(true) + .setProgressMonitor(gitOperationsPrinter) + .setURI(projectGitUrl) + .call() + } + + git.checkout() + .setName(gitCommitSha) + .setProgressMonitor(gitOperationsPrinter) + .call() + + return projectRepoDir + } + + fun repoApplyPatch(patchFile: PatchFile) { + println("Applying patch $patchFile to repository") + val patch = File(patchFile) + Git.open(projectRepoDir) + .apply() + .setPatch(patch.inputStream()) + .call() + } + + fun repoReset() { + println("Hard resetting project repo") + Git.open(projectRepoDir) + .reset() + .setMode(ResetCommand.ResetType.HARD) + .setProgressMonitor(gitOperationsPrinter) + .call() + } + + fun runBenchmark( + scenarioSuite: ScenarioSuite, + benchmarkName: BenchmarkName, + dryRun: Boolean = false + ): BenchmarkResult { + println("Staring benchmark $benchmarkName") + val normalizedBenchmarkName = benchmarkName.normalizeTitle + if (!scenariosDir.exists()) scenariosDir.mkdirs() + val scenarioFile = scenariosDir.resolve("${projectName}_$normalizedBenchmarkName.scenario") + scenarioSuite.writeTo(scenarioFile) + val benchmarkOutputDir = benchmarkOutputsDir + .resolve(projectName) + .resolve(normalizedBenchmarkName) + .also { + if (it.exists()) it.deleteRecursively() + it.mkdirs() + } + + val profilerProcessBuilder = ProcessBuilder() + .directory(workingDir) + .inheritIO() + .command( + gradleProfilerBin.absolutePath, + "--benchmark", + "--measure-config-time", + "--project-dir", + projectRepoDir.absolutePath, + "--scenario-file", + scenarioFile.absolutePath, + "--output-dir", + benchmarkOutputDir.absolutePath + ).also { + // Required, so 'gradle-profiler' will use toolchain JDK instead of current user one + it.environment()["JAVA_HOME"] = System.getProperty("java.home") + if (dryRun) it.command().add("--dry-run") + } + + val profilerProcess = profilerProcessBuilder.start() + // Stop profiler on script stop + Runtime.getRuntime().addShutdownHook(Thread { + profilerProcess.destroy() + }) + profilerProcess.waitFor() + + if (profilerProcess.exitValue() != 0) { + throw BenchmarkFailedException(profilerProcess.exitValue()) + } + + println("Benchmark $benchmarkName has ended") + return BenchmarkResult( + benchmarkName, + benchmarkOutputDir.resolve("benchmark.csv") + ) + } + + fun aggregateBenchmarkResults(vararg benchmarkResults: BenchmarkResult) { + println("Aggregating benchmark results...") + val results = benchmarkResults + .map { result -> + DataFrame + .readCSV(result.result) + .flipColumnsWithRows() + .remove("version", "tasks") // removing unused columns + .remove { nameContains("warm-up build") } // removing warm-up times + .add("median build time") { // squashing build times into median build time + valuesOf().median(typeOf()) + } + .remove { nameContains("measured build") } // removing iterations results + .groupBy("scenario").aggregate { // merging configuration and build times into one row + first { it.values().contains("task start") }["median build time"] into "tasks start median time" + first { it.values().contains("execution") }["median build time"] into "execution median time" + } + .add("benchmark") { result.name } + } + .concat() + .groupBy("scenario").aggregate { // Merging scenarios from different benchmarks into one row + forEachRow { row -> + row["tasks start median time"] into "Configuration: ${row["benchmark"]}" + row["execution median time"] into "Execution: ${row["benchmark"]}" + } + } + .sortBy("scenario") + .rename("scenario" to "Scenario") + .reorderColumnsBy { + // "Scenario" column should always be in the first place + if (it.name() == "Scenario") "0000Scenario" else it.name() + } + + println("Benchmark results:") + println(results.print(borders = true)) + + val benchmarkOutputDir = benchmarkOutputsDir.resolve(projectName) + val benchmarkCsv = benchmarkOutputDir.resolve("$projectName.csv") + results.writeCSV(benchmarkCsv) + val benchmarkHtml = benchmarkOutputDir.resolve("$projectName.html") + benchmarkHtml.writeText( + results.toHTML( + includeInit = true + ).toString() + ) + println("Result in csv format: ${benchmarkCsv.absolutePath}") + println("Result in html format: ${benchmarkHtml.absolutePath}") + } + + private fun downloadAndExtractGradleProfiler() { + println("Downloading gradle-profiler into ${gradleProfilerDir.absolutePath}") + + gradleProfilerDir.mkdirs() + + val okHttpClient = OkHttpClient() + val request = Request.Builder() + .get() + .url(GRADLE_PROFILER_URL) + .build() + val response = okHttpClient.newCall(request).execute() + if (!response.isSuccessful) { + throw IOException("Failed to download gradle-profiler, error code: ${response.code}") + } + + val contentLength = response.body!!.contentLength() + var downloadedLength = 0L + print("Downloading: ") + response.body!!.byteStream().buffered().use { responseContent -> + ZipInputStream(responseContent).use { zip -> + var zipEntry = zip.nextEntry + while (zipEntry != null) { + if (zipEntry.isDirectory) { + gradleProfilerDir.resolve(zipEntry.name.dropLeadingDir).also { it.mkdirs() } + } else { + gradleProfilerDir.resolve(zipEntry.name.dropLeadingDir).outputStream().buffered().use { + zip.copyTo(it) + } + } + downloadedLength += zipEntry.compressedSize + print("..${downloadedLength * 100 / contentLength}%") + zip.closeEntry() + zipEntry = zip.nextEntry + } + } + print("\n") + } + gradleProfilerBin.setExecutable(true) + println("Finished downloading gradle-profiler") + } + + private fun ScenarioSuite.writeTo(output: File) { + output.writeText( + """ + |default-scenarios = [${scenarios.joinToString { "\"${it.title.normalizeTitle}\"" }}] + | + |${scenarios.joinToString(separator = "\n") { it.write() }} + """.trimMargin() + ) + } + + private fun Scenario.write(): String = + """ + |${title.normalizeTitle} { + | title = "$title" + | warm-ups = $warmups + | iterations = $iterations + | tasks = [${tasks.joinToString { "\"$it\"" }}] + | ${if (gradleArgs.isNotEmpty()) "gradle-args = [${gradleArgs.joinToString { "\"$it\"" }}]" else ""} + | ${if (cleanupTasks.isNotEmpty()) "cleanup-tasks = [${cleanupTasks.joinToString { "\"$it\"" }}]" else ""} + | ${if (applyAbiChange.isNotEmpty()) "apply-abi-change-to = [${applyAbiChange.joinToString { "\"$it\"" }}]" else ""} + |} + """.trimMargin() + + private val String.dropLeadingDir: String get() = substringAfter('/') + private val String.normalizeTitle: String get() = lowercase().replace(" ", "_") + + private fun DataFrame<*>.flipColumnsWithRows(): DataFrame<*> { + val firstColumn = columns().first() + return DataFrameBuilder( + listOf(firstColumn.name) + firstColumn.values.map { it.toString() } + ).withColumns { columnName -> + when { + columnName == "scenario" -> DataColumn.createValueColumn( + columnName, + columns().map { + it.name.dropLastWhile { it.isDigit() } + }.drop(1) + ) + columnName == "version" -> DataColumn.createValueColumn( + columnName, + rowToColumn(columnName) { it.toString() } + ) + columnName == "tasks" -> DataColumn.createValueColumn( + columnName, + rowToColumn(columnName) { it.toString() } + ) + columnName == "value" -> DataColumn.createValueColumn( + columnName, + rowToColumn(columnName) { it.toString() } + ) + columnName.startsWith("warm-up build") -> DataColumn.createValueColumn( + columnName, + rowToColumn(columnName) { it.toString().toInt() } + ) + columnName.startsWith("measured build") -> DataColumn.createValueColumn( + columnName, + rowToColumn(columnName) { it.toString().toInt() } + ) + else -> throw IllegalArgumentException("Unknown column name: $columnName") + } + } + } + + private fun DataFrame<*>.rowToColumn( + rowName: String, + typeConversion: (Any?) -> T + ): List = + rows().first { it.values().first() == rowName }.values().drop(1).map { typeConversion(it) } + + private val gradleProfilerBin: File + get() = gradleProfilerDir + .resolve("bin") + .run { + if (System.getProperty("os.name").contains("windows", ignoreCase = true)) { + resolve("gradle-profiler.bat") + } else { + resolve("gradle-profiler").also { it.setExecutable(true) } + } + } + + companion object { + private const val STEP_SEPARATOR = "###############" + private const val GRADLE_PROFILER_VERSION = "0.16.0" + private const val GRADLE_PROFILER_URL: String = + "https://repo.gradle.org/gradle/ext-releases-local/org/gradle/profiler/gradle-profiler/$GRADLE_PROFILER_VERSION/gradle-profiler-$GRADLE_PROFILER_VERSION.zip" + + } +} + +typealias BenchmarkName = String +typealias PatchFile = String + +class BenchmarkFailedException(exitCode: Int) : Exception( + "Benchmark process was not finished successfully with $exitCode exit code." +) + +class BenchmarkResult( + val name: BenchmarkName, + val result: File +) + +@Target(AnnotationTarget.FILE) +@Retention(AnnotationRetention.SOURCE) +annotation class BenchmarkProject( + val name: String, + val gitUrl: String, + val gitCommitSha: String +) + +class BenchmarkScriptDefinition : ScriptCompilationConfiguration( + { + defaultImports( + listOf( + BenchmarkProject::class.qualifiedName!!, + "org.jetbrains.kotlin.gradle.benchmark.suite" + ) + ) + refineConfiguration { + onAnnotations(BenchmarkProject::class, handler = BenchmarkScriptConfigurator()) + } + } +) + +class BenchmarkEvaluationConfiguration : ScriptEvaluationConfiguration( + { + refineConfigurationBeforeEvaluate { context -> + val compileConf = context.evaluationConfiguration[compilationConfiguration] + ?: return@refineConfigurationBeforeEvaluate makeFailureResult("Script compilation configuration is not available") + + val name: String = compileConf[benchmarkProjectName]!! + val gitUrl: String = compileConf[benchmarkGitUrl]!! + val gitCommitSha: String = compileConf[benchmarkGitCommitSha]!! + + context.evaluationConfiguration.with evalConf@{ + constructorArgs(name, gitUrl, gitCommitSha) + }.asSuccess() + } + } +) + +internal class BenchmarkScriptConfigurator : RefineScriptCompilationConfigurationHandler { + override fun invoke( + context: ScriptConfigurationRefinementContext + ): ResultWithDiagnostics { + val benchmarkProject = context.collectedData + ?.get(ScriptCollectedData.foundAnnotations) + ?.find { it is BenchmarkProject } as? BenchmarkProject + ?: return run { + makeFailureResult("Script does not contain ${BenchmarkProject::name} annotation!") + } + + return ScriptCompilationConfiguration(context.compilationConfiguration) { + data[benchmarkProjectName] = benchmarkProject.name + data[benchmarkGitUrl] = benchmarkProject.gitUrl + data[benchmarkGitCommitSha] = benchmarkProject.gitCommitSha + }.asSuccess() + } +} + +private val benchmarkProjectName by PropertiesCollection.key() +private val benchmarkGitUrl by PropertiesCollection.key() +private val benchmarkGitCommitSha by PropertiesCollection.key() \ No newline at end of file diff --git a/libraries/tools/gradle/regression-benchmark-templates/src/main/resources/META-INF/kotlin/script/templates/org.jetbrains.kotlin.gradle.benchmark.BenchmarkTemplate b/libraries/tools/gradle/regression-benchmark-templates/src/main/resources/META-INF/kotlin/script/templates/org.jetbrains.kotlin.gradle.benchmark.BenchmarkTemplate new file mode 100644 index 00000000000..e69de29bb2d diff --git a/license/third_party/jgit_license.txt b/license/third_party/jgit_license.txt new file mode 100644 index 00000000000..63acd9e5ef7 --- /dev/null +++ b/license/third_party/jgit_license.txt @@ -0,0 +1,15 @@ +https://www.eclipse.org/jgit/ + +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/license/third_party/okhttp_license.txt b/license/third_party/okhttp_license.txt new file mode 100644 index 00000000000..9b57b260d70 --- /dev/null +++ b/license/third_party/okhttp_license.txt @@ -0,0 +1,15 @@ +https://github.com/square/okhttp/ + +Copyright 2019 Square, Inc. + +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. diff --git a/license/third_party/sl4f_license.txt b/license/third_party/sl4f_license.txt new file mode 100644 index 00000000000..fc8ffabe368 --- /dev/null +++ b/license/third_party/sl4f_license.txt @@ -0,0 +1,25 @@ +http://www.slf4j.org + + MIT License + + Copyright (c) 2004-2017 QOS.ch + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/settings.gradle b/settings.gradle index ad310e2ec8c..950f79c403e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -237,6 +237,7 @@ include ":benchmarks", ":kotlin-gradle-plugin-test-utils-embeddable", ":kotlin-gradle-plugin-integration-tests", ":gradle:android-test-fixes", + ":gradle:regression-benchmark-templates", ":kotlin-tooling-metadata", ":kotlin-allopen", ":kotlin-noarg", @@ -624,6 +625,7 @@ project(':kotlin-gradle-plugin-model').projectDir = "$rootDir/libraries/tools/ko project(':kotlin-gradle-plugin-test-utils-embeddable').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-test-utils-embeddable" as File project(':kotlin-gradle-plugin-integration-tests').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-integration-tests" as File project(':gradle:android-test-fixes').projectDir = "$rootDir/libraries/tools/gradle/android-test-fixes" as File +project(":gradle:regression-benchmark-templates").projectDir = "$rootDir/libraries/tools/gradle/regression-benchmark-templates" as File project(':kotlin-tooling-metadata').projectDir = "$rootDir/libraries/tools/kotlin-tooling-metadata" as File project(':kotlin-allopen').projectDir = "$rootDir/libraries/tools/kotlin-allopen" as File project(':kotlin-noarg').projectDir = "$rootDir/libraries/tools/kotlin-noarg" as File