Move everything under kotlin-native folder
I was forced to manually do update the following files, because otherwise they would be ignored according .gitignore settings. Probably they should be deleted from repo. Interop/.idea/compiler.xml Interop/.idea/gradle.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml Interop/.idea/modules.xml Interop/.idea/modules/Indexer/Indexer.iml Interop/.idea/modules/Runtime/Runtime.iml Interop/.idea/modules/StubGenerator/StubGenerator.iml backend.native/backend.native.iml backend.native/bc.frontend/bc.frontend.iml backend.native/cli.bc/cli.bc.iml backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt backend.native/tests/link/lib/foo.kt backend.native/tests/link/lib/foo2.kt backend.native/tests/teamcity-test.property
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
// Report with changes of different fields.
|
||||
class ChangeReport<T>(val entityName: String, val changes: List<FieldChange<T>>) {
|
||||
fun renderAsTextReport(): String {
|
||||
var content = ""
|
||||
if (!changes.isEmpty()) {
|
||||
content = "$content$entityName changes\n"
|
||||
content = "$content====================\n"
|
||||
changes.forEach {
|
||||
content = "$content${it.renderAsText()}"
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
// Change of report field.
|
||||
class FieldChange<T>(val field: String, val previous: T, val current: T) {
|
||||
companion object {
|
||||
fun <T> getFieldChangeOrNull(field: String, previous: T, current: T): FieldChange<T>? {
|
||||
if (previous != current) {
|
||||
return FieldChange(field, previous, current)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun renderAsText(): String {
|
||||
return "$field: $previous -> $current\n"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.MeanVariance
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
val MeanVariance.description: String
|
||||
get() {
|
||||
val format = { number: Double -> number.format(2) }
|
||||
return "${format(mean)} ± ${format(variance)}"
|
||||
}
|
||||
|
||||
val MeanVarianceBenchmark.description: String
|
||||
get() = "${score.format()} ± ${variance.format()}"
|
||||
|
||||
// Calculate difference in percentage compare to another.
|
||||
fun MeanVarianceBenchmark.calcPercentageDiff(other: MeanVarianceBenchmark): MeanVariance {
|
||||
assert(other.score >= 0 &&
|
||||
other.variance >= 0 &&
|
||||
other.score - other.variance != 0.0,
|
||||
{ "Mean and variance should be positive and not equal!" })
|
||||
// Analyze intervals. Calculate difference between border points.
|
||||
val (bigValue, smallValue) = if (score > other.score) Pair(this, other) else Pair(other, this)
|
||||
val bigValueIntervalStart = bigValue.score - bigValue.variance
|
||||
val bigValueIntervalEnd = bigValue.score + bigValue.variance
|
||||
val smallValueIntervalStart = smallValue.score - smallValue.variance
|
||||
val smallValueIntervalEnd = smallValue.score + smallValue.variance
|
||||
if (smallValueIntervalEnd > bigValueIntervalStart) {
|
||||
// Interval intersect.
|
||||
return MeanVariance(0.0, 0.0)
|
||||
}
|
||||
val mean = ((smallValueIntervalEnd - bigValueIntervalStart) / bigValueIntervalStart) *
|
||||
(if (score > other.score) -1 else 1)
|
||||
|
||||
val maxValueChange = ((bigValueIntervalEnd - smallValueIntervalEnd) / bigValueIntervalEnd)
|
||||
val minValueChange = ((bigValueIntervalStart - smallValueIntervalStart) / bigValueIntervalStart)
|
||||
val variance = abs(abs(mean) - max(minValueChange, maxValueChange))
|
||||
return MeanVariance(mean * 100, variance * 100)
|
||||
}
|
||||
|
||||
// Calculate ratio value compare to another.
|
||||
fun MeanVarianceBenchmark.calcRatio(other: MeanVarianceBenchmark): MeanVariance {
|
||||
assert(other.score >= 0 &&
|
||||
other.variance >= 0 &&
|
||||
other.score - other.variance != 0.0,
|
||||
{ "Mean and variance should be positive and not equal!" })
|
||||
val mean = score / other.score
|
||||
val minRatio = (score - variance) / (other.score + other.variance)
|
||||
val maxRatio = (score + variance) / (other.score - other.variance)
|
||||
val ratioConfInt = min(abs(minRatio - mean), abs(maxRatio - mean))
|
||||
return MeanVariance(mean, ratioConfInt)
|
||||
}
|
||||
|
||||
fun geometricMean(values: Collection<Double>, totalNumber: Int = values.size) =
|
||||
with(values.asSequence().filter { it != 0.0 }) {
|
||||
if (count() == 0) {
|
||||
0.0
|
||||
} else {
|
||||
map { it.pow(1.0 / totalNumber) }.reduce { a, b -> a * b }
|
||||
}
|
||||
}
|
||||
|
||||
fun computeMeanVariance(samples: List<Double>): MeanVariance {
|
||||
val removedBroadSamples = 0.2
|
||||
val zStar = 1.67 // Critical point for 90% confidence of normal distribution.
|
||||
// Skip several minimal and maximum values.
|
||||
val filteredSamples = if (samples.size >= 1/removedBroadSamples) {
|
||||
samples.sorted().subList((samples.size * removedBroadSamples).toInt(),
|
||||
samples.size - (samples.size * removedBroadSamples).toInt())
|
||||
} else {
|
||||
samples
|
||||
}
|
||||
|
||||
val mean = filteredSamples.sum() / filteredSamples.size
|
||||
val variance = samples.indices.sumByDouble {
|
||||
(samples[it] - mean) * (samples[it] - mean)
|
||||
} / samples.size
|
||||
val confidenceInterval = sqrt(variance / samples.size) * zStar
|
||||
return MeanVariance(mean, confidenceInterval)
|
||||
}
|
||||
|
||||
// Calculate average results for benchmarks (each benchmark can be run several times).
|
||||
fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): BenchmarksTable {
|
||||
return benchmarks.map { (name, resultsSet) ->
|
||||
val repeatedSequence = IntArray(resultsSet.size)
|
||||
var metric = BenchmarkResult.Metric.EXECUTION_TIME
|
||||
var currentStatus = BenchmarkResult.Status.PASSED
|
||||
var currentWarmup = -1
|
||||
|
||||
// Results can be already processed.
|
||||
if (resultsSet[0] is MeanVarianceBenchmark) {
|
||||
assert(resultsSet.size == 1) { "Several MeanVarianceBenchmark instances." }
|
||||
name to resultsSet[0] as MeanVarianceBenchmark
|
||||
} else {
|
||||
// Collect common benchmark values and check them.
|
||||
resultsSet.forEachIndexed { index, result ->
|
||||
// If there was at least one failure, summary is marked as failure.
|
||||
if (result.status == BenchmarkResult.Status.FAILED) {
|
||||
currentStatus = result.status
|
||||
}
|
||||
repeatedSequence[index] = result.repeat
|
||||
if (currentWarmup != -1)
|
||||
if (result.warmup != currentWarmup)
|
||||
println("Check data consistency. Warmup value for benchmark '${result.name}' differs.")
|
||||
currentWarmup = result.warmup
|
||||
metric = result.metric
|
||||
}
|
||||
|
||||
repeatedSequence.sort()
|
||||
// Check if there are missed loop during running benchmarks.
|
||||
repeatedSequence.forEachIndexed { index, element ->
|
||||
if (index != 0)
|
||||
if ((element - repeatedSequence[index - 1]) != 1)
|
||||
println("Check data consistency. For benchmark '$name' there is no run" +
|
||||
" between ${repeatedSequence[index - 1]} and $element.")
|
||||
}
|
||||
|
||||
// Create mean and variance benchmarks result.
|
||||
val scoreMeanVariance = computeMeanVariance(resultsSet.map { it.score })
|
||||
val runtimeInUsMeanVariance = computeMeanVariance(resultsSet.map { it.runtimeInUs })
|
||||
val meanBenchmark = MeanVarianceBenchmark(name, currentStatus, scoreMeanVariance.mean, metric,
|
||||
runtimeInUsMeanVariance.mean, repeatedSequence[resultsSet.size - 1],
|
||||
currentWarmup, scoreMeanVariance.variance)
|
||||
name to meanBenchmark
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
fun collectBenchmarksDurations(benchmarks: Map<String, List<BenchmarkResult>>): Map<String, Double> =
|
||||
benchmarks.map { (name, resultsSet) ->
|
||||
name to resultsSet.sumByDouble { it.runtimeInUs }
|
||||
}.toMap()
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.BenchmarksReport
|
||||
import org.jetbrains.report.Compiler
|
||||
import org.jetbrains.report.Environment
|
||||
import org.jetbrains.report.MeanVariance
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
import kotlin.math.abs
|
||||
|
||||
typealias SummaryBenchmark = Pair<MeanVarianceBenchmark?, MeanVarianceBenchmark?>
|
||||
typealias BenchmarksTable = Map<String, MeanVarianceBenchmark>
|
||||
typealias SummaryBenchmarksTable = Map<String, SummaryBenchmark>
|
||||
typealias ScoreChange = Pair<MeanVariance, MeanVariance>
|
||||
|
||||
// Summary report with comparasion of separate benchmarks results.
|
||||
class SummaryBenchmarksReport(val currentReport: BenchmarksReport,
|
||||
val previousReport: BenchmarksReport? = null,
|
||||
val meaningfulChangesValue: Double = 0.5) {
|
||||
// Report created by joining comparing reports.
|
||||
val mergedReport: Map<String, SummaryBenchmark>
|
||||
private val benchmarksDurations: Map<String, Pair<Double?, Double?>>
|
||||
|
||||
// Lists of benchmarks in different status.
|
||||
private val benchmarksWithChangedStatus = mutableListOf<FieldChange<BenchmarkResult.Status>>()
|
||||
|
||||
// Maps with changes of performance.
|
||||
var regressions = mapOf<String, ScoreChange>()
|
||||
private set
|
||||
var improvements = mapOf<String, ScoreChange>()
|
||||
private set
|
||||
|
||||
// Summary value of report - geometric mean.
|
||||
val geoMeanBenchmark: SummaryBenchmark
|
||||
var geoMeanScoreChange: ScoreChange? = null
|
||||
private set
|
||||
|
||||
// Environment and tools.
|
||||
val environments: Pair<Environment, Environment?>
|
||||
val compilers: Pair<Compiler, Compiler?>
|
||||
|
||||
// Countable properties.
|
||||
val failedBenchmarks: List<String>
|
||||
get() = mergedReport.filter { it.value.first?.status == BenchmarkResult.Status.FAILED }
|
||||
.map { it.key }
|
||||
|
||||
val addedBenchmarks: List<String>
|
||||
get() = mergedReport.filter { it.value.second == null }.map { it.key }
|
||||
|
||||
val removedBenchmarks: List<String>
|
||||
get() = mergedReport.filter { it.value.first == null }.map { it.key }
|
||||
|
||||
val benchmarksNumber: Int
|
||||
get() = mergedReport.keys.size
|
||||
|
||||
val currentMeanVarianceBenchmarks: List<MeanVarianceBenchmark>
|
||||
get() = mergedReport.filter { it.value.first != null }.map { it.value.first!! }
|
||||
|
||||
val currentBenchmarksDuration: Map<String, Double>
|
||||
get() = benchmarksDurations.filter { it.value.first != null }.map { it.key to it.value.first!! }.toMap()
|
||||
|
||||
val maximumRegression: Double
|
||||
get() = getMaximumChange(regressions)
|
||||
|
||||
val maximumImprovement: Double
|
||||
get() = getMaximumChange(improvements)
|
||||
|
||||
val regressionsGeometricMean: Double
|
||||
get() = getGeometricMeanOfChanges(regressions)
|
||||
|
||||
val improvementsGeometricMean: Double
|
||||
get() = getGeometricMeanOfChanges(improvements)
|
||||
|
||||
val envChanges: List<FieldChange<String>>
|
||||
get() {
|
||||
val previousEnvironment = environments.second
|
||||
val currentEnvironment = environments.first
|
||||
return previousEnvironment?.let {
|
||||
mutableListOf<FieldChange<String>>().apply {
|
||||
addFieldChange("Machine CPU", previousEnvironment.machine.cpu, currentEnvironment.machine.cpu)
|
||||
addFieldChange("Machine OS", previousEnvironment.machine.os, currentEnvironment.machine.os)
|
||||
addFieldChange("JDK version", previousEnvironment.jdk.version, currentEnvironment.jdk.version)
|
||||
addFieldChange("JDK vendor", previousEnvironment.jdk.vendor, currentEnvironment.jdk.vendor)
|
||||
}
|
||||
} ?: listOf<FieldChange<String>>()
|
||||
}
|
||||
|
||||
val kotlinChanges: List<FieldChange<String>>
|
||||
get() {
|
||||
val previousCompiler = compilers.second
|
||||
val currentCompiler = compilers.first
|
||||
return previousCompiler?.let {
|
||||
mutableListOf<FieldChange<String>>().apply {
|
||||
addFieldChange("Backend type", previousCompiler.backend.type.type, currentCompiler.backend.type.type)
|
||||
addFieldChange("Backend version", previousCompiler.backend.version, currentCompiler.backend.version)
|
||||
addFieldChange("Backend flags", previousCompiler.backend.flags.toString(),
|
||||
currentCompiler.backend.flags.toString())
|
||||
addFieldChange("Kotlin version", previousCompiler.kotlinVersion, currentCompiler.kotlinVersion)
|
||||
}
|
||||
} ?: listOf<FieldChange<String>>()
|
||||
}
|
||||
|
||||
init {
|
||||
// Count avarage values for each benchmark.
|
||||
val currentBenchmarksTable = collectMeanResults(currentReport.benchmarks)
|
||||
val previousBenchmarksTable = previousReport?.let {
|
||||
collectMeanResults(previousReport.benchmarks)
|
||||
}
|
||||
mergedReport = createMergedReport(currentBenchmarksTable, previousBenchmarksTable)
|
||||
benchmarksDurations = calculateBenchmarksDuration(currentReport, previousReport)
|
||||
geoMeanBenchmark = calculateGeoMeanBenchmark(currentBenchmarksTable, previousBenchmarksTable)
|
||||
environments = Pair(currentReport.env, previousReport?.env)
|
||||
compilers = Pair(currentReport.compiler, previousReport?.compiler)
|
||||
|
||||
if (previousReport != null) {
|
||||
// Check changes in environment and tools.
|
||||
analyzePerformanceChanges()
|
||||
}
|
||||
}
|
||||
|
||||
// Get benchmark report.
|
||||
fun getBenchmarksReport(takeMainReport: Boolean = true) =
|
||||
if (takeMainReport)
|
||||
BenchmarksReport(environments.first, mergedReport.map { (_, value) -> value.first!! }, compilers.first)
|
||||
else
|
||||
BenchmarksReport(environments.second!!, mergedReport.map { (_, value) -> value.second!! }, compilers.second!!)
|
||||
|
||||
fun getResultsByMetric(metric: BenchmarkResult.Metric, getGeoMean: Boolean = true, filter: List<String>? = null,
|
||||
normalizeData: Map<String, Map<String, Double>>? = null): List<Double?> {
|
||||
val benchmarks = filter?.let {
|
||||
mergedReport.filter { entry ->
|
||||
filter.find {
|
||||
entry.key.startsWith(it)
|
||||
} != null
|
||||
}
|
||||
} ?: mergedReport
|
||||
val results = benchmarks.map { entry ->
|
||||
val name = entry.key.removeSuffix(metric.suffix)
|
||||
if (entry.value.first!!.metric == metric) {
|
||||
val score = entry.value.first!!.score
|
||||
val value = normalizeData?.let {
|
||||
it.get(name)?.get("$metric")?.let { score / it }
|
||||
?: error("No normalization data for benchmark $name and metric $metric")
|
||||
} ?: score
|
||||
name to value
|
||||
} else name to null
|
||||
}.toMap()
|
||||
if (getGeoMean) {
|
||||
return listOf(geometricMean(results.values.filterNotNull()))
|
||||
}
|
||||
return filter?.let { it.map { results[it] }.toList() } ?: results.values.toList()
|
||||
}
|
||||
|
||||
private fun getMaximumChange(bucket: Map<String, ScoreChange>): Double =
|
||||
// Maps of regressions and improvements are sorted.
|
||||
if (bucket.isEmpty()) 0.0 else bucket.values.map { it.first.mean }.first()
|
||||
|
||||
private fun getGeometricMeanOfChanges(bucket: Map<String, ScoreChange>): Double {
|
||||
if (bucket.isEmpty())
|
||||
return 0.0
|
||||
var percentsList = bucket.values.map { it.first.mean }
|
||||
return if (percentsList.first() > 0.0) {
|
||||
geometricMean(percentsList, benchmarksNumber)
|
||||
} else {
|
||||
// Geometric mean can be counted on positive numbers.
|
||||
percentsList = percentsList.map { abs(it) }
|
||||
-geometricMean(percentsList, benchmarksNumber)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBenchmarksWithChangedStatus(): List<FieldChange<BenchmarkResult.Status>> = benchmarksWithChangedStatus
|
||||
|
||||
// Create geometric mean.
|
||||
private fun createGeoMeanBenchmark(benchTable: BenchmarksTable): MeanVarianceBenchmark {
|
||||
val geoMeanBenchmarkName = "Geometric mean"
|
||||
val geoMean = geometricMean(benchTable.toList().map { (_, value) -> value.score })
|
||||
val varianceGeoMean = geometricMean(benchTable.toList().map { (_, value) -> value.variance })
|
||||
return MeanVarianceBenchmark(geoMeanBenchmarkName, geoMean, varianceGeoMean)
|
||||
}
|
||||
|
||||
// Generate map with summary durations of each benchmark.
|
||||
private fun calculateBenchmarksDuration(currentReport: BenchmarksReport, previousReport: BenchmarksReport?):
|
||||
Map<String, Pair<Double?, Double?>> {
|
||||
val currentDurations = collectBenchmarksDurations(currentReport.benchmarks)
|
||||
val previousDurations = previousReport?.let {
|
||||
collectBenchmarksDurations(previousReport.benchmarks)
|
||||
} ?: mapOf<String, Double>()
|
||||
return currentDurations.keys.union(previousDurations.keys)
|
||||
.map { it to Pair(currentDurations[it], previousDurations[it]) }.toMap()
|
||||
}
|
||||
|
||||
// Merge current and compare to report.
|
||||
private fun createMergedReport(currentBenchmarks: BenchmarksTable, previousBenchmarks: BenchmarksTable?):
|
||||
Map<String, SummaryBenchmark> {
|
||||
val mergedTable = mutableMapOf<String, SummaryBenchmark>()
|
||||
mergedTable.apply {
|
||||
currentBenchmarks.forEach { (name, current) ->
|
||||
// Check existance of benchmark in previous results.
|
||||
if (previousBenchmarks == null || name !in previousBenchmarks) {
|
||||
getOrPut(name) { SummaryBenchmark(current, null) }
|
||||
} else {
|
||||
val previousBenchmark = previousBenchmarks.getValue(name)
|
||||
getOrPut(name) { SummaryBenchmark(current, previousBenchmarks[name]) }
|
||||
// Explore change of status.
|
||||
if (previousBenchmark.status != current.status) {
|
||||
val statusChange = FieldChange("$name", previousBenchmark.status, current.status)
|
||||
benchmarksWithChangedStatus.add(statusChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add removed benchmarks to merged report.
|
||||
mergedTable.apply {
|
||||
previousBenchmarks?.filter { (key, _) -> key !in currentBenchmarks }?.forEach { (key, value) ->
|
||||
getOrPut(key) { SummaryBenchmark(null, value) }
|
||||
}
|
||||
}
|
||||
|
||||
return mergedTable
|
||||
}
|
||||
|
||||
// Calculate geometric mean.
|
||||
private fun calculateGeoMeanBenchmark(currentBenchmarks: BenchmarksTable, previousBenchmarks: BenchmarksTable?):
|
||||
SummaryBenchmark {
|
||||
// Calculate geometric mean.
|
||||
val currentGeoMean = createGeoMeanBenchmark(currentBenchmarks)
|
||||
val previousGeoMean = previousBenchmarks?.let { createGeoMeanBenchmark(previousBenchmarks) }
|
||||
return SummaryBenchmark(currentGeoMean, previousGeoMean)
|
||||
}
|
||||
|
||||
private fun getBenchmarkPerfomanceChange(name: String, benchmark: SummaryBenchmark): Pair<String, ScoreChange>? {
|
||||
val (current, previous) = benchmark
|
||||
current?.let {
|
||||
previous?.let {
|
||||
// Calculate metrics for showing difference.
|
||||
val percent = current.calcPercentageDiff(previous)
|
||||
val ratio = current.calcRatio(previous)
|
||||
if (abs(percent.mean) - percent.variance >= meaningfulChangesValue) {
|
||||
return Pair(name, Pair(percent, ratio))
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Analyze and collect changes in performance between same becnhmarks.
|
||||
private fun analyzePerformanceChanges() {
|
||||
val performanceChanges = mergedReport.asSequence().map { (name, element) ->
|
||||
getBenchmarkPerfomanceChange(name, element)
|
||||
}.filterNotNull().groupBy {
|
||||
if (it.second.first.mean > 0) "regressions" else "improvements"
|
||||
}
|
||||
|
||||
// Sort regressions and improvements.
|
||||
regressions = performanceChanges["regressions"]
|
||||
?.sortedByDescending { it.second.first.mean }?.map { it.first to it.second }
|
||||
?.toMap() ?: mapOf<String, ScoreChange>()
|
||||
improvements = performanceChanges["improvements"]
|
||||
?.sortedBy { it.second.first.mean }?.map { it.first to it.second }
|
||||
?.toMap() ?: mapOf<String, ScoreChange>()
|
||||
|
||||
// Calculate change for geometric mean.
|
||||
val (current, previous) = geoMeanBenchmark
|
||||
geoMeanScoreChange = current?.let {
|
||||
previous?.let {
|
||||
Pair(current.calcPercentageDiff(previous), current.calcRatio(previous))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> MutableList<FieldChange<T>>.addFieldChange(field: String, previous: T, current: T) {
|
||||
FieldChange.getFieldChangeOrNull(field, previous, current)?.let {
|
||||
add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
expect fun readFile(fileName: String): String
|
||||
expect fun Double.format(decimalNumber: Int = 4): String
|
||||
expect fun writeToFile(fileName: String, text: String)
|
||||
expect fun assert(value: Boolean, lazyMessage: () -> Any)
|
||||
expect fun sendGetRequest(url: String, user: String? = null, password: String? = null,
|
||||
followLocation: Boolean = false) : String
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.report
|
||||
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
interface JsonSerializable {
|
||||
fun serializeFields(): String
|
||||
|
||||
fun toJson(): String {
|
||||
return """
|
||||
{
|
||||
${serializeFields()}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
// Convert iterable objects arrays, lists to json.
|
||||
fun <T> arrayToJson(data: Iterable<T>): String {
|
||||
return data.joinToString(prefix = "[", postfix = "]") {
|
||||
if (it is JsonSerializable) it.toJson() else it.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface EntityFromJsonFactory<T> : ConvertedFromJson {
|
||||
fun create(data: JsonElement): T
|
||||
}
|
||||
|
||||
// Parse array with benchmarks to list
|
||||
fun parseBenchmarksArray(data: JsonElement): List<BenchmarkResult> {
|
||||
if (data is JsonArray) {
|
||||
return data.jsonArray.map {
|
||||
if (MeanVarianceBenchmark.isMeanVarianceBenchmark(it))
|
||||
MeanVarianceBenchmark.create(it as JsonObject)
|
||||
else BenchmarkResult.create(it as JsonObject)
|
||||
}
|
||||
} else {
|
||||
error("Benchmarks field is expected to be an array. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
// Class for benchmarks report with all information of run.
|
||||
open class BenchmarksReport(val env: Environment, benchmarksList: List<BenchmarkResult>, val compiler: Compiler) :
|
||||
JsonSerializable {
|
||||
|
||||
companion object : EntityFromJsonFactory<BenchmarksReport> {
|
||||
override fun create(data: JsonElement): BenchmarksReport {
|
||||
if (data is JsonObject) {
|
||||
val env = Environment.create(data.getRequiredField("env"))
|
||||
val benchmarksObj = data.getRequiredField("benchmarks")
|
||||
val compiler = Compiler.create(data.getRequiredField("kotlin"))
|
||||
val buildNumberField = data.getOptionalField("buildNumber")
|
||||
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
||||
val report = BenchmarksReport(env, benchmarksList, compiler)
|
||||
buildNumberField?.let { report.buildNumber = (it as JsonLiteral).unquoted() }
|
||||
return report
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
// Made a map of becnhmarks with name as key from list.
|
||||
private fun structBenchmarks(benchmarksList: List<BenchmarkResult>) =
|
||||
benchmarksList.groupBy { it.name }
|
||||
}
|
||||
|
||||
val benchmarks = structBenchmarks(benchmarksList)
|
||||
|
||||
var buildNumber: String? = null
|
||||
|
||||
override fun serializeFields(): String {
|
||||
val buildNumberField = buildNumber?.let {
|
||||
""",
|
||||
"buildNumber": "$buildNumber"
|
||||
"""
|
||||
} ?: ""
|
||||
return """
|
||||
"env": ${env.toJson()},
|
||||
"kotlin": ${compiler.toJson()},
|
||||
"benchmarks": ${arrayToJson(benchmarks.flatMap { it.value })}$buildNumberField
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
fun merge(other: BenchmarksReport): BenchmarksReport {
|
||||
val mergedBenchmarks = HashMap(benchmarks)
|
||||
other.benchmarks.forEach {
|
||||
if (it.key in mergedBenchmarks) {
|
||||
error("${it.key} already exists in report!")
|
||||
}
|
||||
}
|
||||
mergedBenchmarks.putAll(other.benchmarks)
|
||||
return BenchmarksReport(env, mergedBenchmarks.flatMap { it.value }, compiler)
|
||||
}
|
||||
|
||||
// Concatenate benchmarks report if they have same environment and compiler.
|
||||
operator fun plus(other: BenchmarksReport): BenchmarksReport {
|
||||
if (compiler != other.compiler || env != other.env) {
|
||||
error("It's impossible to concat reports from different machines!")
|
||||
}
|
||||
return merge(other)
|
||||
}
|
||||
}
|
||||
|
||||
// Class for kotlin compiler
|
||||
data class Compiler(val backend: Backend, val kotlinVersion: String) : JsonSerializable {
|
||||
|
||||
enum class BackendType(val type: String) {
|
||||
JVM("jvm"),
|
||||
NATIVE("native")
|
||||
}
|
||||
|
||||
companion object : EntityFromJsonFactory<Compiler> {
|
||||
override fun create(data: JsonElement): Compiler {
|
||||
if (data is JsonObject) {
|
||||
val backend = Backend.create(data.getRequiredField("backend"))
|
||||
val kotlinVersion = elementToString(data.getRequiredField("kotlinVersion"), "kotlinVersion")
|
||||
|
||||
return Compiler(backend, kotlinVersion)
|
||||
} else {
|
||||
error("Kotlin entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
fun backendTypeFromString(s: String): BackendType? = BackendType.values().find { it.type == s }
|
||||
}
|
||||
|
||||
// Class for compiler backend
|
||||
data class Backend(val type: BackendType, val version: String, val flags: List<String>) : JsonSerializable {
|
||||
companion object : EntityFromJsonFactory<Backend> {
|
||||
override fun create(data: JsonElement): Backend {
|
||||
if (data is JsonObject) {
|
||||
val typeElement = data.getRequiredField("type")
|
||||
if (typeElement is JsonLiteral) {
|
||||
val type = backendTypeFromString(typeElement.unquoted())
|
||||
?: error("Backend type should be 'jvm' or 'native'")
|
||||
val version = elementToString(data.getRequiredField("version"), "version")
|
||||
val flagsArray = data.getOptionalField("flags")
|
||||
var flags: List<String> = emptyList()
|
||||
if (flagsArray != null && flagsArray is JsonArray) {
|
||||
flags = flagsArray.jsonArray.map { it.toString() }
|
||||
}
|
||||
return Backend(type, version, flags)
|
||||
} else {
|
||||
error("Backend type should be string literal.")
|
||||
}
|
||||
} else {
|
||||
error("Backend entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
val result = """
|
||||
"type": "${type.type}",
|
||||
"version": "${version}""""
|
||||
// Don't print flags field if there is no one.
|
||||
if (flags.isEmpty()) {
|
||||
return """$result
|
||||
"""
|
||||
} else {
|
||||
return """
|
||||
$result,
|
||||
"flags": ${arrayToJson(flags.map { if (it.startsWith("\"")) it else "\"$it\"" })}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
"backend": ${backend.toJson()},
|
||||
"kotlinVersion": "${kotlinVersion}"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
// Class for description of environment of benchmarks run
|
||||
data class Environment(val machine: Machine, val jdk: JDKInstance) : JsonSerializable {
|
||||
|
||||
companion object : EntityFromJsonFactory<Environment> {
|
||||
override fun create(data: JsonElement): Environment {
|
||||
if (data is JsonObject) {
|
||||
val machine = Machine.create(data.getRequiredField("machine"))
|
||||
val jdk = JDKInstance.create(data.getRequiredField("jdk"))
|
||||
|
||||
return Environment(machine, jdk)
|
||||
} else {
|
||||
error("Environment entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Class for description of machine used for benchmarks run.
|
||||
data class Machine(val cpu: String, val os: String) : JsonSerializable {
|
||||
companion object : EntityFromJsonFactory<Machine> {
|
||||
override fun create(data: JsonElement): Machine {
|
||||
if (data is JsonObject) {
|
||||
val cpu = elementToString(data.getRequiredField("cpu"), "cpu")
|
||||
val os = elementToString(data.getRequiredField("os"), "os")
|
||||
|
||||
return Machine(cpu, os)
|
||||
} else {
|
||||
error("Machine entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
"cpu": "$cpu",
|
||||
"os": "$os"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
// Class for description of jdk used for benchmarks run.
|
||||
data class JDKInstance(val version: String, val vendor: String) : JsonSerializable {
|
||||
companion object : EntityFromJsonFactory<JDKInstance> {
|
||||
override fun create(data: JsonElement): JDKInstance {
|
||||
if (data is JsonObject) {
|
||||
val version = elementToString(data.getRequiredField("version"), "version")
|
||||
val vendor = elementToString(data.getRequiredField("vendor"), "vendor")
|
||||
|
||||
return JDKInstance(version, vendor)
|
||||
} else {
|
||||
error("JDK entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
"version": "$version",
|
||||
"vendor": "$vendor"
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
"machine": ${machine.toJson()},
|
||||
"jdk": ${jdk.toJson()}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
open class BenchmarkResult(val name: String, val status: Status,
|
||||
val score: Double, val metric: Metric, val runtimeInUs: Double,
|
||||
val repeat: Int, val warmup: Int) : JsonSerializable {
|
||||
|
||||
enum class Metric(val suffix: String, val value: String) {
|
||||
EXECUTION_TIME("", "EXECUTION_TIME"),
|
||||
CODE_SIZE(".codeSize", "CODE_SIZE"),
|
||||
COMPILE_TIME(".compileTime", "COMPILE_TIME"),
|
||||
BUNDLE_SIZE(".bundleSize", "BUNDLE_SIZE")
|
||||
}
|
||||
|
||||
constructor(name: String, score: Double) : this(name, Status.PASSED, score, Metric.EXECUTION_TIME, 0.0, 0, 0)
|
||||
|
||||
companion object : EntityFromJsonFactory<BenchmarkResult> {
|
||||
|
||||
override fun create(data: JsonElement): BenchmarkResult {
|
||||
if (data is JsonObject) {
|
||||
var name = elementToString(data.getRequiredField("name"), "name")
|
||||
val metricElement = data.getOptionalField("metric")
|
||||
val metric = if (metricElement != null && metricElement is JsonLiteral)
|
||||
metricFromString(metricElement.unquoted()) ?: Metric.EXECUTION_TIME
|
||||
else Metric.EXECUTION_TIME
|
||||
name += metric.suffix
|
||||
val statusElement = data.getRequiredField("status")
|
||||
if (statusElement is JsonLiteral) {
|
||||
val status = statusFromString(statusElement.unquoted())
|
||||
?: error("Status should be PASSED or FAILED")
|
||||
|
||||
val score = elementToDouble(data.getRequiredField("score"), "score")
|
||||
val runtimeInUs = elementToDouble(data.getRequiredField("runtimeInUs"), "runtimeInUs")
|
||||
val repeat = elementToInt(data.getRequiredField("repeat"), "repeat")
|
||||
val warmup = elementToInt(data.getRequiredField("warmup"), "warmup")
|
||||
|
||||
return BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup)
|
||||
} else {
|
||||
error("Status should be string literal.")
|
||||
}
|
||||
} else {
|
||||
error("Benchmark entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
fun statusFromString(s: String): Status? = Status.values().find { it.value == s }
|
||||
fun metricFromString(s: String): Metric? = Metric.values().find { it.value == s }
|
||||
}
|
||||
|
||||
enum class Status(val value: String) {
|
||||
PASSED("PASSED"),
|
||||
FAILED("FAILED")
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
"name": "${name.removeSuffix(metric.suffix)}",
|
||||
"status": "${status.value}",
|
||||
"score": ${score},
|
||||
"metric": "${metric.value}",
|
||||
"runtimeInUs": ${runtimeInUs},
|
||||
"repeat": ${repeat},
|
||||
"warmup": ${warmup}
|
||||
"""
|
||||
}
|
||||
|
||||
val shortName: String
|
||||
get() = name.removeSuffix(metric.suffix)
|
||||
}
|
||||
|
||||
// Entity to describe avarage values which conssists of mean and variance values.
|
||||
data class MeanVariance(val mean: Double, val variance: Double)
|
||||
|
||||
// Processed benchmark result with calculated mean and variance value.
|
||||
open class MeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
|
||||
runtimeInUs: Double, repeat: Int, warmup: Int, val variance: Double) :
|
||||
BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup) {
|
||||
|
||||
constructor(name: String, score: Double, variance: Double) : this(name, BenchmarkResult.Status.PASSED, score,
|
||||
BenchmarkResult.Metric.EXECUTION_TIME, 0.0, 0, 0, variance)
|
||||
|
||||
companion object : EntityFromJsonFactory<MeanVarianceBenchmark> {
|
||||
|
||||
fun isMeanVarianceBenchmark(data: JsonElement) = data is JsonObject && data.getOptionalField("variance") != null
|
||||
|
||||
override fun create(data: JsonElement): MeanVarianceBenchmark {
|
||||
if (data is JsonObject) {
|
||||
val baseBenchmark = BenchmarkResult.create(data)
|
||||
val variance = elementToDouble(data.getRequiredField("variance"), "variance")
|
||||
return MeanVarianceBenchmark(baseBenchmark.name, baseBenchmark.status, baseBenchmark.score, baseBenchmark.metric,
|
||||
baseBenchmark.runtimeInUs, baseBenchmark.repeat, baseBenchmark.warmup, variance)
|
||||
} else {
|
||||
error("Benchmark entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
${super.serializeFields()},
|
||||
"variance": $variance
|
||||
"""
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.report.json
|
||||
|
||||
// Entity can be created from json description.
|
||||
interface ConvertedFromJson {
|
||||
// Methods for conversion to expected type with checks of possibility of such conversions.
|
||||
fun elementToDouble(element: JsonElement, name: String): Double =
|
||||
if (element is JsonPrimitive)
|
||||
element.double
|
||||
else
|
||||
error("Field '$name' in '$element' is expected to be a double number. Please, check origin files.")
|
||||
|
||||
fun elementToInt(element: JsonElement, name: String): Int =
|
||||
if (element is JsonPrimitive)
|
||||
element.int
|
||||
else
|
||||
error("Field '$name' in '$element' is expected to be an integer number. Please, check origin files.")
|
||||
|
||||
fun elementToString(element: JsonElement, name:String): String =
|
||||
if (element is JsonLiteral)
|
||||
element.unquoted()
|
||||
else
|
||||
error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
|
||||
|
||||
fun elementToStringOrNull(element: JsonElement, name:String): String? =
|
||||
when (element) {
|
||||
is JsonLiteral -> element.unquoted()
|
||||
is JsonNull -> null
|
||||
else -> error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonObject.getRequiredField(fieldName: String): JsonElement {
|
||||
return getOrNull(fieldName) ?: error("Field '$fieldName' doesn't exist in '$this'. Please, check origin files.")
|
||||
}
|
||||
|
||||
fun JsonObject.getOptionalField(fieldName: String): JsonElement? {
|
||||
return getOrNull(fieldName)
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.report.json
|
||||
|
||||
/**
|
||||
* Class representing single JSON element.
|
||||
* Can be [JsonPrimitive], [JsonArray] or [JsonObject].
|
||||
*
|
||||
* [JsonElement.toString] properly prints JSON tree as valid JSON, taking into
|
||||
* account quoted values and primitives
|
||||
*/
|
||||
sealed class JsonElement {
|
||||
|
||||
/**
|
||||
* Convenience method to get current element as [JsonPrimitive]
|
||||
* @throws JsonElementTypeMismatchException is current element is not a [JsonPrimitive]
|
||||
*/
|
||||
open val primitive: JsonPrimitive
|
||||
get() = error("JsonLiteral")
|
||||
|
||||
/**
|
||||
* Convenience method to get current element as [JsonObject]
|
||||
* @throws JsonElementTypeMismatchException is current element is not a [JsonObject]
|
||||
*/
|
||||
open val jsonObject: JsonObject
|
||||
get() = error("JsonObject")
|
||||
|
||||
/**
|
||||
* Convenience method to get current element as [JsonArray]
|
||||
* @throws JsonElementTypeMismatchException is current element is not a [JsonArray]
|
||||
*/
|
||||
open val jsonArray: JsonArray
|
||||
get() = error("JsonArray")
|
||||
|
||||
/**
|
||||
* Convenience method to get current element as [JsonNull]
|
||||
* @throws JsonElementTypeMismatchException is current element is not a [JsonNull]
|
||||
*/
|
||||
open val jsonNull: JsonNull
|
||||
get() = error("JsonPrimitive")
|
||||
|
||||
/**
|
||||
* Checks whether current element is [JsonNull]
|
||||
*/
|
||||
val isNull: Boolean
|
||||
get() = this === JsonNull
|
||||
|
||||
private fun error(element: String): Nothing =
|
||||
throw JsonElementTypeMismatchException(this::class.toString(), element)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing JSON primitive value. Can be either [JsonLiteral] or [JsonNull].
|
||||
*/
|
||||
sealed class JsonPrimitive : JsonElement() {
|
||||
|
||||
/**
|
||||
* Content of given element without quotes. For [JsonNull] this methods returns `"null"`
|
||||
*/
|
||||
abstract val content: String
|
||||
|
||||
/**
|
||||
* Content of the given element without quotes or `null` if current element is [JsonNull]
|
||||
*/
|
||||
abstract val contentOrNull: String?
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
final override val primitive: JsonPrimitive = this
|
||||
|
||||
/**
|
||||
* Returns content of current element as int
|
||||
* @throws NumberFormatException if current element is not a valid representation of number
|
||||
*/
|
||||
val int: Int get() = content.toInt()
|
||||
|
||||
/**
|
||||
* Returns content of current element as int or `null` if current element is not a valid representation of number
|
||||
**/
|
||||
val intOrNull: Int? get() = content.toIntOrNull()
|
||||
|
||||
/**
|
||||
* Returns content of current element as long
|
||||
* @throws NumberFormatException if current element is not a valid representation of number
|
||||
*/
|
||||
val long: Long get() = content.toLong()
|
||||
|
||||
/**
|
||||
* Returns content of current element as long or `null` if current element is not a valid representation of number
|
||||
*/
|
||||
val longOrNull: Long? get() = content.toLongOrNull()
|
||||
|
||||
/**
|
||||
* Returns content of current element as double
|
||||
* @throws NumberFormatException if current element is not a valid representation of number
|
||||
*/
|
||||
val double: Double get() = content.toDouble()
|
||||
|
||||
/**
|
||||
* Returns content of current element as double or `null` if current element is not a valid representation of number
|
||||
*/
|
||||
val doubleOrNull: Double? get() = content.toDoubleOrNull()
|
||||
|
||||
/**
|
||||
* Returns content of current element as float
|
||||
* @throws NumberFormatException if current element is not a valid representation of number
|
||||
*/
|
||||
val float: Float get() = content.toFloat()
|
||||
|
||||
/**
|
||||
* Returns content of current element as float or `null` if current element is not a valid representation of number
|
||||
*/
|
||||
val floatOrNull: Float? get() = content.toFloatOrNull()
|
||||
|
||||
/**
|
||||
* Returns content of current element as boolean
|
||||
* @throws IllegalStateException if current element doesn't represent boolean
|
||||
*/
|
||||
val boolean: Boolean get() = content.toBooleanStrict()
|
||||
|
||||
/**
|
||||
* Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
|
||||
*/
|
||||
val booleanOrNull: Boolean? get() = content.toBooleanStrictOrNull()
|
||||
|
||||
override fun toString() = content
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing JSON literals: numbers, booleans and string.
|
||||
* Strings are always quoted.
|
||||
*/
|
||||
data class JsonLiteral internal constructor(
|
||||
private val body: Any,
|
||||
private val isString: Boolean
|
||||
) : JsonPrimitive() {
|
||||
|
||||
override val content = body.toString()
|
||||
override val contentOrNull: String = content
|
||||
|
||||
/**
|
||||
* Creates number literal
|
||||
*/
|
||||
constructor(number: Number) : this(number, false)
|
||||
|
||||
/**
|
||||
* Creates boolean literal
|
||||
*/
|
||||
constructor(boolean: Boolean) : this(boolean, false)
|
||||
|
||||
/**
|
||||
* Creates quoted string literal
|
||||
*/
|
||||
constructor(string: String) : this(string, true)
|
||||
|
||||
override fun toString() =
|
||||
if (isString) buildString { printQuoted(content) }
|
||||
else content
|
||||
|
||||
fun unquoted() = content
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing JSON `null` value
|
||||
*/
|
||||
object JsonNull : JsonPrimitive() {
|
||||
override val jsonNull: JsonNull = this
|
||||
override val content: String = "null"
|
||||
override val contentOrNull: String? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing JSON object, consisting of name-value pairs, where value is arbitrary [JsonElement]
|
||||
*/
|
||||
data class JsonObject(val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
|
||||
|
||||
override val jsonObject: JsonObject = this
|
||||
|
||||
/**
|
||||
* Returns [JsonElement] associated with given [key]
|
||||
* @throws NoSuchElementException if element is not present
|
||||
*/
|
||||
override fun get(key: String): JsonElement = content[key] ?: throw NoSuchElementException("Element $key is missing")
|
||||
|
||||
/**
|
||||
* Returns [JsonElement] associated with given [key] or `null` if element is not present
|
||||
*/
|
||||
fun getOrNull(key: String): JsonElement? = content[key]
|
||||
|
||||
/**
|
||||
* Returns [JsonPrimitive] associated with given [key]
|
||||
*
|
||||
* @throws NoSuchElementException if element is not present
|
||||
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
|
||||
*/
|
||||
fun getPrimitive(key: String): JsonPrimitive = get(key) as? JsonPrimitive
|
||||
?: unexpectedJson(key, "JsonPrimitive")
|
||||
|
||||
/**
|
||||
* Returns [JsonObject] associated with given [key]
|
||||
*
|
||||
* @throws NoSuchElementException if element is not present
|
||||
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
|
||||
*/
|
||||
fun getObject(key: String): JsonObject = get(key) as? JsonObject
|
||||
?: unexpectedJson(key, "JsonObject")
|
||||
|
||||
/**
|
||||
* Returns [JsonArray] associated with given [key]
|
||||
*
|
||||
* @throws NoSuchElementException if element is not present
|
||||
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
|
||||
*/
|
||||
fun getArray(key: String): JsonArray = get(key) as? JsonArray
|
||||
?: unexpectedJson(key, "JsonArray")
|
||||
|
||||
/**
|
||||
* Returns [JsonPrimitive] associated with given [key] or `null` if element
|
||||
* is not present or has different type
|
||||
*/
|
||||
fun getPrimitiveOrNull(key: String): JsonPrimitive? = content[key] as? JsonPrimitive
|
||||
|
||||
/**
|
||||
* Returns [JsonObject] associated with given [key] or `null` if element
|
||||
* is not present or has different type
|
||||
*/
|
||||
fun getObjectOrNull(key: String): JsonObject? = content[key] as? JsonObject
|
||||
|
||||
/**
|
||||
* Returns [JsonArray] associated with given [key] or `null` if element
|
||||
* is not present or has different type
|
||||
*/
|
||||
fun getArrayOrNull(key: String): JsonArray? = content[key] as? JsonArray
|
||||
|
||||
/**
|
||||
* Returns [J] associated with given [key]
|
||||
*
|
||||
* @throws NoSuchElementException if element is not present
|
||||
* @throws JsonElementTypeMismatchException if element is present, but has invalid type
|
||||
*/
|
||||
inline fun <reified J : JsonElement> getAs(key: String): J = get(key) as? J
|
||||
?: unexpectedJson(key, J::class.toString())
|
||||
|
||||
/**
|
||||
* Returns [J] associated with given [key] or `null` if element
|
||||
* is not present or has different type
|
||||
*/
|
||||
inline fun <reified J : JsonElement> lookup(key: String): J? = content[key] as? J
|
||||
|
||||
override fun toString(): String {
|
||||
return content.entries.joinToString(
|
||||
prefix = "{",
|
||||
postfix = "}",
|
||||
transform = {(k, v) -> """"$k": $v"""}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class JsonArray(val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
|
||||
|
||||
override val jsonArray: JsonArray = this
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonPrimitive]
|
||||
* @throws JsonElementTypeMismatchException if element has invalid type
|
||||
*/
|
||||
fun getPrimitive(index: Int) = content[index] as? JsonPrimitive
|
||||
?: unexpectedJson("at $index", "JsonPrimitive")
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonObject]
|
||||
* @throws JsonElementTypeMismatchException if element has invalid type
|
||||
*/
|
||||
fun getObject(index: Int) = content[index] as? JsonObject
|
||||
?: unexpectedJson("at $index", "JsonObject")
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonArray]
|
||||
* @throws JsonElementTypeMismatchException if element has invalid type
|
||||
*/
|
||||
fun getArray(index: Int) = content[index] as? JsonArray
|
||||
?: unexpectedJson("at $index", "JsonArray")
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonPrimitive] or `null` if element is missing or has different type
|
||||
*/
|
||||
fun getPrimitiveOrNull(index: Int) = content.getOrNull(index) as? JsonPrimitive
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonObject] or `null` if element is missing or has different type
|
||||
*/
|
||||
fun getObjectOrNull(index: Int) = content.getOrNull(index) as? JsonObject
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [JsonArray] or `null` if element is missing or has different type
|
||||
*/
|
||||
fun getArrayOrNull(index: Int) = content.getOrNull(index) as? JsonArray
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [J]
|
||||
* @throws JsonElementTypeMismatchException if element has invalid type
|
||||
*/
|
||||
inline fun <reified J : JsonElement> getAs(index: Int): J = content[index] as? J
|
||||
?: unexpectedJson("at $index", J::class.toString())
|
||||
|
||||
/**
|
||||
* Returns [index]-th element of an array as [J] or `null` if element is missing or has different type
|
||||
*/
|
||||
inline fun <reified J : JsonElement> getAsOrNull(index: Int): J? = content.getOrNull(index) as? J
|
||||
|
||||
override fun toString() = content.joinToString(prefix = "[", postfix = "]")
|
||||
}
|
||||
|
||||
fun unexpectedJson(key: String, expected: String): Nothing =
|
||||
throw JsonElementTypeMismatchException(key, expected)
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.report.json
|
||||
|
||||
class JsonInvalidValueInStrictModeException(value: Any, valueDescription: String) : Exception(
|
||||
"$value is not a valid $valueDescription as per JSON spec.\n" +
|
||||
"You can disable strict mode to serialize such values"
|
||||
) {
|
||||
constructor(floatValue: Float) : this(floatValue, "float")
|
||||
constructor(doubleValue: Double) : this(doubleValue, "double")
|
||||
}
|
||||
|
||||
class JsonUnknownKeyException(key: String) : Exception(
|
||||
"Strict JSON encountered unknown key: $key\n" +
|
||||
"You can disable strict mode to skip unknown keys"
|
||||
)
|
||||
|
||||
class JsonParsingException(position: Int, message: String) : Exception("Invalid JSON at $position: $message")
|
||||
|
||||
class JsonElementTypeMismatchException(key: String, expected: String) : Exception("Element $key is not a $expected")
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.report.json
|
||||
|
||||
import org.jetbrains.report.json.EscapeCharMappings.ESC2C
|
||||
// special strings
|
||||
internal const val NULL = "null"
|
||||
|
||||
// special chars
|
||||
internal const val COMMA = ','
|
||||
internal const val COLON = ':'
|
||||
internal const val BEGIN_OBJ = '{'
|
||||
internal const val END_OBJ = '}'
|
||||
internal const val BEGIN_LIST = '['
|
||||
internal const val END_LIST = ']'
|
||||
internal const val STRING = '"'
|
||||
internal const val STRING_ESC = '\\'
|
||||
internal const val INVALID = 0.toChar()
|
||||
internal const val UNICODE_ESC = 'u'
|
||||
// token classes
|
||||
internal const val TC_OTHER: Byte = 0
|
||||
internal const val TC_STRING: Byte = 1
|
||||
internal const val TC_STRING_ESC: Byte = 2
|
||||
internal const val TC_WS: Byte = 3
|
||||
internal const val TC_COMMA: Byte = 4
|
||||
internal const val TC_COLON: Byte = 5
|
||||
internal const val TC_BEGIN_OBJ: Byte = 6
|
||||
internal const val TC_END_OBJ: Byte = 7
|
||||
internal const val TC_BEGIN_LIST: Byte = 8
|
||||
internal const val TC_END_LIST: Byte = 9
|
||||
internal const val TC_NULL: Byte = 10
|
||||
internal const val TC_INVALID: Byte = 11
|
||||
internal const val TC_EOF: Byte = 12
|
||||
// mapping from chars to token classes
|
||||
private const val CTC_MAX = 0x7e
|
||||
// mapping from escape chars real chars
|
||||
private const val C2ESC_MAX = 0x5d
|
||||
private const val ESC2C_MAX = 0x75
|
||||
|
||||
internal val C2TC = ByteArray(CTC_MAX).apply {
|
||||
for (i in 0..0x20)
|
||||
initC2TC(i, TC_INVALID)
|
||||
initC2TC(0x09, TC_WS)
|
||||
initC2TC(0x0a, TC_WS)
|
||||
initC2TC(0x0d, TC_WS)
|
||||
initC2TC(0x20, TC_WS)
|
||||
initC2TC(COMMA, TC_COMMA)
|
||||
initC2TC(COLON, TC_COLON)
|
||||
initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
|
||||
initC2TC(END_OBJ, TC_END_OBJ)
|
||||
initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
|
||||
initC2TC(END_LIST, TC_END_LIST)
|
||||
initC2TC(STRING, TC_STRING)
|
||||
initC2TC(STRING_ESC, TC_STRING_ESC)
|
||||
}
|
||||
// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC]
|
||||
internal object EscapeCharMappings {
|
||||
internal val ESC2C = CharArray(ESC2C_MAX)
|
||||
internal val C2ESC = CharArray(C2ESC_MAX).apply {
|
||||
for (i in 0x00..0x1f)
|
||||
initC2ESC(i, UNICODE_ESC)
|
||||
initC2ESC(0x08, 'b')
|
||||
initC2ESC(0x09, 't')
|
||||
initC2ESC(0x0a, 'n')
|
||||
initC2ESC(0x0c, 'f')
|
||||
initC2ESC(0x0d, 'r')
|
||||
initC2ESC('/', '/')
|
||||
initC2ESC(STRING, STRING)
|
||||
initC2ESC(STRING_ESC, STRING_ESC)
|
||||
}
|
||||
private fun CharArray.initC2ESC(c: Int, esc: Char) {
|
||||
this[c] = esc
|
||||
if (esc != UNICODE_ESC) ESC2C[esc.toInt()] = c.toChar()
|
||||
}
|
||||
private fun CharArray.initC2ESC(c: Char, esc: Char) = initC2ESC(c.toInt(), esc)
|
||||
}
|
||||
private fun ByteArray.initC2TC(c: Int, cl: Byte) {
|
||||
this[c] = cl
|
||||
}
|
||||
private fun ByteArray.initC2TC(c: Char, cl: Byte) {
|
||||
initC2TC(c.toInt(), cl)
|
||||
}
|
||||
internal fun charToTokenClass(c: Char) = if (c.toInt() < CTC_MAX) C2TC[c.toInt()] else TC_OTHER
|
||||
internal fun escapeToChar(c: Int): Char = if (c < ESC2C_MAX) ESC2C[c] else INVALID
|
||||
// JSON low level parser
|
||||
internal class Parser(val source: String) {
|
||||
var curPos: Int = 0 // position in source
|
||||
private set
|
||||
// updated by nextToken
|
||||
var tokenPos: Int = 0
|
||||
private set
|
||||
var tc: Byte = TC_EOF
|
||||
private set
|
||||
// update by nextString/nextLiteral
|
||||
private var offset = -1 // when offset >= 0 string is in source, otherwise in buf
|
||||
private var length = 0 // length of string
|
||||
private var buf = CharArray(16) // only used for strings with escapes
|
||||
init {
|
||||
nextToken()
|
||||
}
|
||||
internal inline fun requireTc(expected: Byte, lazyErrorMsg: () -> String) {
|
||||
if (tc != expected)
|
||||
fail(tokenPos, lazyErrorMsg())
|
||||
}
|
||||
val canBeginValue: Boolean
|
||||
get() = when (tc) {
|
||||
TC_BEGIN_LIST, TC_BEGIN_OBJ, TC_OTHER, TC_STRING, TC_NULL -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun takeStr(): String {
|
||||
if (tc != TC_OTHER && tc != TC_STRING) fail(tokenPos, "Expected string or non-null literal")
|
||||
val prevStr = if (offset < 0)
|
||||
buf.concatToString(0, length) else
|
||||
source.substring(offset, offset + length)
|
||||
nextToken()
|
||||
return prevStr
|
||||
}
|
||||
private fun append(ch: Char) {
|
||||
if (length >= buf.size) buf = buf.copyOf(2 * buf.size)
|
||||
buf[length++] = ch
|
||||
}
|
||||
// initializes buf usage upon the first encountered escaped char
|
||||
private fun appendRange(source: String, fromIndex: Int, toIndex: Int) {
|
||||
val addLen = toIndex - fromIndex
|
||||
val oldLen = length
|
||||
val newLen = oldLen + addLen
|
||||
if (newLen > buf.size) buf = buf.copyOf(newLen.coerceAtLeast(2 * buf.size))
|
||||
for (i in 0 until addLen) buf[oldLen + i] = source[fromIndex + i]
|
||||
length += addLen
|
||||
}
|
||||
fun nextToken() {
|
||||
val source = source
|
||||
var curPos = curPos
|
||||
val maxLen = source.length
|
||||
while (true) {
|
||||
if (curPos >= maxLen) {
|
||||
tokenPos = curPos
|
||||
tc = TC_EOF
|
||||
return
|
||||
}
|
||||
val ch = source[curPos]
|
||||
val tc = charToTokenClass(ch)
|
||||
when (tc) {
|
||||
TC_WS -> curPos++ // skip whitespace
|
||||
TC_OTHER -> {
|
||||
nextLiteral(source, curPos)
|
||||
return
|
||||
}
|
||||
TC_STRING -> {
|
||||
nextString(source, curPos)
|
||||
return
|
||||
}
|
||||
else -> {
|
||||
this.tokenPos = curPos
|
||||
this.tc = tc
|
||||
this.curPos = curPos + 1
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun nextLiteral(source: String, startPos: Int) {
|
||||
tokenPos = startPos
|
||||
offset = startPos
|
||||
var curPos = startPos
|
||||
val maxLen = source.length
|
||||
while (true) {
|
||||
curPos++
|
||||
if (curPos >= maxLen || charToTokenClass(source[curPos]) != TC_OTHER) break
|
||||
}
|
||||
this.curPos = curPos
|
||||
length = curPos - offset
|
||||
tc = if (rangeEquals(source, offset, length, NULL)) TC_NULL else TC_OTHER
|
||||
}
|
||||
private fun nextString(source: String, startPos: Int) {
|
||||
tokenPos = startPos
|
||||
length = 0 // in buffer
|
||||
var curPos = startPos + 1
|
||||
var lastPos = curPos
|
||||
val maxLen = source.length
|
||||
parse@ while (true) {
|
||||
if (curPos >= maxLen) fail(curPos, "Unexpected end in string")
|
||||
if (source[curPos] == STRING) {
|
||||
break@parse
|
||||
} else if (source[curPos] == STRING_ESC) {
|
||||
appendRange(source, lastPos, curPos)
|
||||
val newPos = appendEsc(source, curPos + 1)
|
||||
curPos = newPos
|
||||
lastPos = newPos
|
||||
} else {
|
||||
curPos++
|
||||
}
|
||||
}
|
||||
if (lastPos == startPos + 1) {
|
||||
// there was no escaped chars
|
||||
this.offset = lastPos
|
||||
this.length = curPos - lastPos
|
||||
} else {
|
||||
// some escaped chars were there
|
||||
appendRange(source, lastPos, curPos)
|
||||
this.offset = -1
|
||||
}
|
||||
this.curPos = curPos + 1
|
||||
tc = TC_STRING
|
||||
}
|
||||
private fun appendEsc(source: String, startPos: Int): Int {
|
||||
var curPos = startPos
|
||||
require(curPos < source.length, curPos) { "Unexpected end after escape char" }
|
||||
val curChar = source[curPos++]
|
||||
if (curChar == UNICODE_ESC) {
|
||||
curPos = appendHex(source, curPos)
|
||||
} else {
|
||||
val c = escapeToChar(curChar.toInt())
|
||||
require(c != INVALID, curPos) { "Invalid escaped char '$curChar'" }
|
||||
append(c)
|
||||
}
|
||||
return curPos
|
||||
}
|
||||
private fun appendHex(source: String, startPos: Int): Int {
|
||||
var curPos = startPos
|
||||
append(
|
||||
((fromHexChar(source, curPos++) shl 12) +
|
||||
(fromHexChar(source, curPos++) shl 8) +
|
||||
(fromHexChar(source, curPos++) shl 4) +
|
||||
fromHexChar(source, curPos++)).toChar()
|
||||
)
|
||||
return curPos
|
||||
}
|
||||
fun skipElement() {
|
||||
if (tc != TC_BEGIN_OBJ && tc != TC_BEGIN_LIST) {
|
||||
nextToken()
|
||||
return
|
||||
}
|
||||
val tokenStack = mutableListOf<Byte>()
|
||||
do {
|
||||
when (tc) {
|
||||
TC_BEGIN_LIST, TC_BEGIN_OBJ -> tokenStack.add(tc)
|
||||
TC_END_LIST -> {
|
||||
if (tokenStack.last() != TC_BEGIN_LIST) throw JsonParsingException(curPos, "found ] instead of }")
|
||||
tokenStack.removeAt(tokenStack.size - 1)
|
||||
}
|
||||
TC_END_OBJ -> {
|
||||
if (tokenStack.last() != TC_BEGIN_OBJ) throw JsonParsingException(curPos, "found } instead of ]")
|
||||
tokenStack.removeAt(tokenStack.size - 1)
|
||||
}
|
||||
}
|
||||
nextToken()
|
||||
} while (tokenStack.isNotEmpty())
|
||||
}
|
||||
}
|
||||
// Utility functions
|
||||
private fun fromHexChar(source: String, curPos: Int): Int {
|
||||
require(curPos < source.length, curPos) { "Unexpected end in unicode escape" }
|
||||
val curChar = source[curPos]
|
||||
return when (curChar) {
|
||||
in '0'..'9' -> curChar.toInt() - '0'.toInt()
|
||||
in 'a'..'f' -> curChar.toInt() - 'a'.toInt() + 10
|
||||
in 'A'..'F' -> curChar.toInt() - 'A'.toInt() + 10
|
||||
else -> fail(curPos, "Invalid toHexChar char '$curChar' in unicode escape")
|
||||
}
|
||||
}
|
||||
private fun rangeEquals(source: String, start: Int, length: Int, str: String): Boolean {
|
||||
val n = str.length
|
||||
if (length != n) return false
|
||||
for (i in 0 until n) if (source[start + i] != str[i]) return false
|
||||
return true
|
||||
}
|
||||
internal inline fun require(condition: Boolean, pos: Int, msg: () -> String) {
|
||||
if (!condition)
|
||||
fail(pos, msg())
|
||||
}
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
internal inline fun fail(pos: Int, msg: String): Nothing {
|
||||
throw JsonParsingException(pos, msg)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
// A bit changed part of kotlinx.serialization plugin
|
||||
package org.jetbrains.report.json
|
||||
|
||||
class JsonTreeParser internal constructor(private val p: Parser) {
|
||||
|
||||
companion object {
|
||||
fun parse(input: String): JsonElement = JsonTreeParser(input).readFully()
|
||||
}
|
||||
|
||||
constructor(input: String) : this(Parser(input))
|
||||
|
||||
private fun readObject(): JsonElement {
|
||||
p.requireTc(TC_BEGIN_OBJ) { "Expected start of object" }
|
||||
p.nextToken()
|
||||
val result: MutableMap<String, JsonElement> = hashMapOf()
|
||||
while (true) {
|
||||
if (p.tc == TC_COMMA) p.nextToken()
|
||||
if (!p.canBeginValue) break
|
||||
val key = p.takeStr()
|
||||
p.requireTc(TC_COLON) { "Expected ':'" }
|
||||
p.nextToken()
|
||||
val elem = read()
|
||||
result[key] = elem
|
||||
}
|
||||
p.requireTc(TC_END_OBJ) { "Expected end of object" }
|
||||
p.nextToken()
|
||||
return JsonObject(result)
|
||||
}
|
||||
|
||||
private fun readValue(isString: Boolean): JsonElement {
|
||||
val str = p.takeStr()
|
||||
return JsonLiteral(str, isString)
|
||||
}
|
||||
|
||||
private fun readArray(): JsonElement {
|
||||
p.requireTc(TC_BEGIN_LIST) { "Expected start of array" }
|
||||
p.nextToken()
|
||||
val result: MutableList<JsonElement> = arrayListOf()
|
||||
while (true) {
|
||||
if (p.tc == TC_COMMA) p.nextToken()
|
||||
if (!p.canBeginValue) break
|
||||
val elem = read()
|
||||
result.add(elem)
|
||||
}
|
||||
p.requireTc(TC_END_LIST) { "Expected end of array" }
|
||||
p.nextToken()
|
||||
return JsonArray(result)
|
||||
}
|
||||
|
||||
fun read(): JsonElement {
|
||||
if (!p.canBeginValue) fail(p.curPos, "Can't begin reading value from here")
|
||||
val tc = p.tc
|
||||
return when (tc) {
|
||||
TC_NULL -> JsonNull.also { p.nextToken() }
|
||||
TC_STRING -> readValue(isString = true)
|
||||
TC_OTHER -> readValue(isString = false)
|
||||
TC_BEGIN_OBJ -> readObject()
|
||||
TC_BEGIN_LIST -> readArray()
|
||||
else -> fail(p.curPos, "Can't begin reading element")
|
||||
}
|
||||
}
|
||||
|
||||
fun readFully(): JsonElement {
|
||||
val r = read()
|
||||
p.requireTc(TC_EOF) { "Input wasn't consumed fully" }
|
||||
return r
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.report.json
|
||||
|
||||
private fun toHexChar(i: Int) : Char {
|
||||
val d = i and 0xf
|
||||
return if (d < 10) (d + '0'.toInt()).toChar()
|
||||
else (d - 10 + 'a'.toInt()).toChar()
|
||||
}
|
||||
|
||||
private val ESCAPE_CHARS: Array<String?> = arrayOfNulls<String>(128).apply {
|
||||
for (c in 0..0x1f) {
|
||||
val c1 = toHexChar(c shr 12)
|
||||
val c2 = toHexChar(c shr 8)
|
||||
val c3 = toHexChar(c shr 4)
|
||||
val c4 = toHexChar(c)
|
||||
this[c] = "\\u$c1$c2$c3$c4"
|
||||
}
|
||||
this['"'.toInt()] = "\\\""
|
||||
this['\\'.toInt()] = "\\\\"
|
||||
this['\t'.toInt()] = "\\t"
|
||||
this['\b'.toInt()] = "\\b"
|
||||
this['\n'.toInt()] = "\\n"
|
||||
this['\r'.toInt()] = "\\r"
|
||||
this[0x0c] = "\\f"
|
||||
}
|
||||
|
||||
internal fun StringBuilder.printQuoted(value: String) {
|
||||
append(STRING)
|
||||
var lastPos = 0
|
||||
val length = value.length
|
||||
for (i in 0 until length) {
|
||||
val c = value[i].toInt()
|
||||
// Do not replace this constant with C2ESC_MAX (which is smaller than ESCAPE_CHARS size),
|
||||
// otherwise JIT won't eliminate range check and won't vectorize this loop
|
||||
if (c >= ESCAPE_CHARS.size) continue // no need to escape
|
||||
val esc = ESCAPE_CHARS[c] ?: continue
|
||||
append(value, lastPos, i) // flush prev
|
||||
append(esc)
|
||||
lastPos = i + 1
|
||||
}
|
||||
append(value, lastPos, length)
|
||||
append(STRING)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
|
||||
* and throws [IllegalStateException] otherwise.
|
||||
*/
|
||||
fun String.toBooleanStrict(): Boolean = toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
|
||||
|
||||
/**
|
||||
* Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
|
||||
* and returns `null` otherwise.
|
||||
*/
|
||||
fun String.toBooleanStrictOrNull(): Boolean? = when {
|
||||
this.equals("true", ignoreCase = true) -> true
|
||||
this.equals("false", ignoreCase = true) -> false
|
||||
else -> null
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
def getHostName() {
|
||||
def target = System.getProperty("os.name")
|
||||
if (target == 'Linux') return 'linux'
|
||||
if (target.startsWith('Windows')) return 'windows'
|
||||
if (target.startsWith('Mac')) return 'macos'
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../benchmarks/shared/src'
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin'
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/tests'
|
||||
}
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
jsTest {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-test-js:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
nativeMain {
|
||||
dependsOn commonMain
|
||||
kotlin.srcDir 'src/main/kotlin-native'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-native'
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin-jvm'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-jvm'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin-js'
|
||||
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-js'
|
||||
}
|
||||
linuxMain { dependsOn nativeMain }
|
||||
windowsMain { dependsOn nativeMain }
|
||||
macosMain {dependsOn nativeMain }
|
||||
}
|
||||
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm') {
|
||||
compilations.all {
|
||||
tasks[compileKotlinTaskName].kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
tasks[compileKotlinTaskName].kotlinOptions.suppressWarnings = true
|
||||
}
|
||||
}
|
||||
|
||||
fromPreset(presets.mingwX64, 'windows') {
|
||||
binaries.all {
|
||||
linkerOpts = ["-L${getMingwPath()}/lib".toString()]
|
||||
}
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly "${getMingwPath()}/include"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fromPreset(presets.linuxX64, 'linux') {
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly '/usr/include', '/usr/include/x86_64-linux-gnu'
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPreset(presets.macosX64, 'macos') {
|
||||
compilations.main.cinterops {
|
||||
libcurl {
|
||||
includeDirs.headerFilterOnly '/opt/local/include', '/usr/local/include'
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPreset(presets.js, 'js') {
|
||||
compilations.main.kotlinOptions {
|
||||
main = "noCall"
|
||||
}
|
||||
}
|
||||
|
||||
configure([windows, linux, macos]) {
|
||||
def isCurrentHost = (name == getHostName())
|
||||
compilations.all {
|
||||
cinterops.all {
|
||||
project.tasks[interopProcessingTaskName].enabled = isCurrentHost
|
||||
}
|
||||
compileKotlinTask.enabled = isCurrentHost
|
||||
}
|
||||
binaries.all {
|
||||
linkTask.enabled = isCurrentHost
|
||||
}
|
||||
|
||||
binaries {
|
||||
executable('benchmarksAnalyzer', [RELEASE]) {
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||
linkerOpts("-L${getMingwPath()}/lib")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js {
|
||||
browser {
|
||||
distribution {
|
||||
directory = new File("$projectDir/web/")
|
||||
}
|
||||
dceTask {
|
||||
keep 'benchmarksAnalyzer.main_kand9s$'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getMingwPath() {
|
||||
def directory = System.getenv("MINGW64_DIR")
|
||||
if (directory == null)
|
||||
directory = "c:/msys64/mingw64"
|
||||
return directory
|
||||
}
|
||||
|
||||
task assembleWeb(type: Sync) {
|
||||
def runtimeDependencies = kotlin.targets.js.compilations.main.runtimeDependencyFiles
|
||||
from(files {
|
||||
runtimeDependencies.collect { File file ->
|
||||
zipTree(file.absolutePath)
|
||||
}
|
||||
}.builtBy(runtimeDependencies)) {
|
||||
includeEmptyDirs = false
|
||||
include { fileTreeElement ->
|
||||
def path = fileTreeElement.path
|
||||
path.endsWith(".js") && (path.startsWith("META-INF/resources/") ||
|
||||
!path.startsWith("META-INF/"))
|
||||
}
|
||||
}
|
||||
|
||||
from compileKotlinJs.destinationDir
|
||||
into "${projectDir}/web"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
# Avoid building platform libraries by the MPP plugin.
|
||||
kotlin.native.distribution.type=prebuilt
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.browser.*
|
||||
import kotlin.js.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
error("Reading from local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
this.asDynamic().toFixed(decimalNumber)
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
if (fileName != "html")
|
||||
error("Writing to local file for JS isn't supported")
|
||||
val bodyPart = text.substringAfter("<body>").substringBefore("</body>")
|
||||
document.body?.innerHTML = bodyPart
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) {
|
||||
if (!value) error(lazyMessage)
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val proxyServerAddress = "https://perf-proxy.labs.jb.gg/"
|
||||
val newUrl = proxyServerAddress + url
|
||||
val request = XMLHttpRequest()
|
||||
|
||||
request.open("GET", newUrl, false, user, password)
|
||||
request.send()
|
||||
if (request.status == 200.toShort()) {
|
||||
return request.responseText
|
||||
}
|
||||
error("Request to $url has status ${request.status}")
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.Base64
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
val inputStream = File(fileName).inputStream()
|
||||
val inputString = inputStream.bufferedReader().use { it.readText() }
|
||||
return inputString
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
"%.${decimalNumber}f".format(this)
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
File(fileName).printWriter().use { out ->
|
||||
out.println(text)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) =
|
||||
kotlin.assert(value, lazyMessage)
|
||||
|
||||
// Create http(-s) request.
|
||||
fun getHttpRequest(url: String, user: String?, password: String?): HttpURLConnection {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
if (user != null && password != null) {
|
||||
val auth = Base64.getEncoder().encode((user + ":" + password).toByteArray()).toString(Charsets.UTF_8)
|
||||
connection.addRequestProperty("Authorization", "Basic $auth")
|
||||
}
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
return connection
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val connection = getHttpRequest(url, user, password)
|
||||
connection.connect()
|
||||
val responseCode = connection.responseCode
|
||||
if (!followLocation) {
|
||||
connection.connect()
|
||||
return connection.inputStream.use { it.reader().use { reader -> reader.readText() } }
|
||||
}
|
||||
|
||||
// Request with redirect.
|
||||
if (responseCode != HttpURLConnection.HTTP_MOVED_TEMP &&
|
||||
responseCode != HttpURLConnection.HTTP_MOVED_PERM &&
|
||||
responseCode != HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
error("No opportunity to redirect, but flag for redirecting to location was provided!")
|
||||
}
|
||||
val newUrl = connection.getHeaderField("Location")
|
||||
val cookies = connection.getHeaderField("Set-Cookie")
|
||||
val redirect = getHttpRequest(newUrl, user, password)
|
||||
redirect.setRequestProperty("Cookie", cookies)
|
||||
redirect.connect()
|
||||
return redirect.inputStream.use { it.reader().use { reader -> reader.readText() } }
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import platform.posix.*
|
||||
import kotlinx.cinterop.*
|
||||
import libcurl.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
val file = fopen(fileName, "r") ?: error("Cannot read file '$fileName'")
|
||||
var buffer = ByteArray(1024)
|
||||
var text = StringBuilder()
|
||||
try {
|
||||
while (true) {
|
||||
val nextLine = fgets(buffer.refTo(0), buffer.size, file)?.toKString()
|
||||
if (nextLine == null) break
|
||||
text.append(nextLine)
|
||||
}
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String {
|
||||
var buffer = ByteArray(1024)
|
||||
snprintf(buffer.refTo(0), buffer.size.toULong(), "%.${decimalNumber}f", this)
|
||||
return buffer.toKString()
|
||||
}
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
val file = fopen(fileName, "wt") ?: error("Cannot write file '$fileName'")
|
||||
try {
|
||||
if (fputs(text, file) == EOF) throw Error("File write error")
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) =
|
||||
kotlin.assert(value, lazyMessage)
|
||||
|
||||
class CUrl(url: String, user: String? = null, password: String? = null, followLocation: Boolean = false) {
|
||||
private val stableRef = StableRef.create(this)
|
||||
|
||||
private val curl = curl_easy_init()
|
||||
|
||||
init {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url)
|
||||
val writeData = staticCFunction(::collectResponse)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData)
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stableRef.asCPointer())
|
||||
if (followLocation) {
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
|
||||
}
|
||||
user ?.let {
|
||||
curl_easy_setopt(curl, CURLOPT_USERNAME, it)
|
||||
}
|
||||
password ?.let {
|
||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, it)
|
||||
}
|
||||
}
|
||||
|
||||
val body = StringBuilder()
|
||||
|
||||
fun fetch() {
|
||||
memScoped {
|
||||
val res = curl_easy_perform(curl)
|
||||
if (res != CURLE_OK)
|
||||
error("curl_easy_perform() failed: ${curl_easy_strerror(res)?.toKString()}")
|
||||
val http_code = alloc<LongVar>()
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code.ptr)
|
||||
if (http_code.value >= 400L) {
|
||||
error("Error http code ${http_code.value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
curl_easy_cleanup(curl)
|
||||
stableRef.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun CPointer<ByteVar>.toKString(length: Int): String {
|
||||
val bytes = this.readBytes(length)
|
||||
return bytes.toKString()
|
||||
}
|
||||
|
||||
fun collectResponse(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
|
||||
buffer ?: return 0u
|
||||
userdata ?. let {
|
||||
val data = buffer.toKString((size * nitems).toInt()).trim()
|
||||
val curl = userdata.asStableRef<CUrl>().get()
|
||||
curl.body.append(data)
|
||||
}
|
||||
return size * nitems
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
val curl = CUrl(url, user, password, followLocation)
|
||||
curl.fetch()
|
||||
curl.close()
|
||||
return curl.body.toString()
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import kotlinx.cli.*
|
||||
import org.jetbrains.analyzer.sendGetRequest
|
||||
import org.jetbrains.analyzer.readFile
|
||||
import org.jetbrains.analyzer.SummaryBenchmarksReport
|
||||
import org.jetbrains.renders.*
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
abstract class Connector {
|
||||
abstract val connectorPrefix: String
|
||||
|
||||
fun isCompatible(fileName: String) =
|
||||
fileName.startsWith(connectorPrefix)
|
||||
|
||||
abstract fun getFileContent(fileLocation: String, user: String? = null): String
|
||||
}
|
||||
|
||||
object ArtifactoryConnector : Connector() {
|
||||
override val connectorPrefix = "artifactory:"
|
||||
val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val fileParametersSize = 3
|
||||
val fileDescription = fileLocation.substringAfter(connectorPrefix)
|
||||
val fileParameters = fileDescription.split(':', limit = fileParametersSize)
|
||||
|
||||
// Right link to Artifactory file.
|
||||
if (fileParameters.size == 1) {
|
||||
val accessFileUrl = "$artifactoryUrl/${fileParameters[0]}"
|
||||
return sendGetRequest(accessFileUrl, followLocation = true)
|
||||
}
|
||||
// Used builds description format.
|
||||
if (fileParameters.size != fileParametersSize) {
|
||||
error("To get file from Artifactory, please, specify, build number from TeamCity and target" +
|
||||
" in format artifactory:build_number:target:filename")
|
||||
}
|
||||
val (buildNumber, target, fileName) = fileParameters
|
||||
val accessFileUrl = "$artifactoryUrl/$target/$buildNumber/$fileName"
|
||||
return sendGetRequest(accessFileUrl, followLocation = true)
|
||||
}
|
||||
}
|
||||
|
||||
object TeamCityConnector : Connector() {
|
||||
override val connectorPrefix = "teamcity:"
|
||||
val teamCityUrl = "http://buildserver.labs.intellij.net"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val fileDescription = fileLocation.substringAfter(connectorPrefix)
|
||||
val buildLocator = fileDescription.substringBeforeLast(':')
|
||||
val fileName = fileDescription.substringAfterLast(':')
|
||||
if (fileDescription == fileLocation ||
|
||||
fileDescription == buildLocator || fileName == fileDescription) {
|
||||
error("To get file from TeamCity, please, specify, build locator and filename on TeamCity" +
|
||||
" in format teamcity:build_locator:filename")
|
||||
}
|
||||
val accessFileUrl = "$teamCityUrl/app/rest/builds/$buildLocator/artifacts/content/$fileName"
|
||||
val userName = user?.substringBefore(':')
|
||||
val password = user?.substringAfter(':')
|
||||
return sendGetRequest(accessFileUrl, userName, password)
|
||||
}
|
||||
}
|
||||
|
||||
object DBServerConnector : Connector() {
|
||||
override val connectorPrefix = ""
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||
|
||||
override fun getFileContent(fileLocation: String, user: String?): String {
|
||||
val buildNumber = fileLocation.substringBefore(':')
|
||||
val target = fileLocation.substringAfter(':')
|
||||
if (target == buildNumber) {
|
||||
error("To get file from database, please, specify, target and build number" +
|
||||
" in format target:build_number")
|
||||
}
|
||||
val accessFileUrl = "$serverUrl/report/$target/$buildNumber"
|
||||
return sendGetRequest(accessFileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileContent(fileName: String, user: String? = null): String {
|
||||
return when {
|
||||
ArtifactoryConnector.isCompatible(fileName) -> ArtifactoryConnector.getFileContent(fileName, user)
|
||||
TeamCityConnector.isCompatible(fileName) -> TeamCityConnector.getFileContent(fileName, user)
|
||||
fileName.endsWith(".json") -> readFile(fileName)
|
||||
else -> DBServerConnector.getFileContent(fileName, user)
|
||||
}
|
||||
}
|
||||
|
||||
fun getBenchmarkReport(fileName: String, user: String? = null): List<BenchmarksReport> {
|
||||
val jsonEntity = JsonTreeParser.parse(getFileContent(fileName, user))
|
||||
return when (jsonEntity) {
|
||||
is JsonObject -> listOf(BenchmarksReport.create(jsonEntity))
|
||||
is JsonArray -> jsonEntity.map { BenchmarksReport.create(it) }
|
||||
else -> error("Wrong format of report. Expected object or array of objects.")
|
||||
}
|
||||
}
|
||||
|
||||
fun parseNormalizeResults(results: String): Map<String, Map<String, Double>> {
|
||||
val parsedNormalizeResults = mutableMapOf<String, MutableMap<String, Double>>()
|
||||
val tokensNumber = 3
|
||||
results.lines().forEach {
|
||||
if (!it.isEmpty()) {
|
||||
val tokens = it.split(",").map { it.trim() }
|
||||
if (tokens.size != tokensNumber) {
|
||||
error("Data for normalization should include benchmark name, metric name and value. Got $it")
|
||||
}
|
||||
parsedNormalizeResults.getOrPut(tokens[0], { mutableMapOf<String, Double>() })[tokens[1]] = tokens[2].toDouble()
|
||||
}
|
||||
}
|
||||
return parsedNormalizeResults
|
||||
}
|
||||
|
||||
fun mergeCompilerFlags(reports: List<BenchmarksReport>): List<String> {
|
||||
val flagsMap = mutableMapOf<String, MutableList<String>>()
|
||||
reports.forEach {
|
||||
val benchmarks = it.benchmarks.values.flatten().asSequence().filter { it.metric == BenchmarkResult.Metric.COMPILE_TIME }
|
||||
.map { it.shortName }.toList()
|
||||
if (benchmarks.isNotEmpty())
|
||||
(flagsMap.getOrPut("${it.compiler.backend.flags.joinToString()}") { mutableListOf<String>() }).addAll(benchmarks)
|
||||
}
|
||||
return flagsMap.map { (flags, benchmarks) -> "$flags for [${benchmarks.distinct().sorted().joinToString()}]" }
|
||||
}
|
||||
|
||||
fun mergeReportsWithDetailedFlags(reports: List<BenchmarksReport>) =
|
||||
if (reports.size > 1) {
|
||||
// Merge reports.
|
||||
val detailedFlags = mergeCompilerFlags(reports)
|
||||
reports.map {
|
||||
BenchmarksReport(it.env, it.benchmarks.values.flatten(),
|
||||
Compiler(Compiler.Backend(it.compiler.backend.type, it.compiler.backend.version, detailedFlags),
|
||||
it.compiler.kotlinVersion))
|
||||
}.reduce { result, it -> result + it }
|
||||
} else {
|
||||
reports.first()
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// Parse args.
|
||||
val argParser = ArgParser("benchmarksAnalyzer")
|
||||
|
||||
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
|
||||
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
|
||||
|
||||
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
|
||||
val epsValue by argParser.option(ArgType.Double, "eps", "e",
|
||||
"Meaningful performance changes").default(1.0)
|
||||
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
|
||||
"Show short version of report").default(false)
|
||||
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
|
||||
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
|
||||
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
|
||||
|
||||
argParser.parse(args)
|
||||
// Read contents of file.
|
||||
val mainBenchsReport = mergeReportsWithDetailedFlags(getBenchmarkReport(mainReport, user))
|
||||
|
||||
var compareToBenchsReport = compareToReport?.let {
|
||||
mergeReportsWithDetailedFlags(getBenchmarkReport(it, user))
|
||||
}
|
||||
|
||||
// Generate comparasion report.
|
||||
val summaryReport = SummaryBenchmarksReport(mainBenchsReport,
|
||||
compareToBenchsReport,
|
||||
epsValue)
|
||||
|
||||
var outputFile = output
|
||||
renders.forEach {
|
||||
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
|
||||
outputFile = null
|
||||
}
|
||||
}
|
||||
+672
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
private fun <T : Comparable<T>> clamp(value: T, minValue: T, maxValue: T): T =
|
||||
minOf(maxOf(value, minValue), maxValue)
|
||||
|
||||
// Natural number.
|
||||
class Natural(initValue: Int) {
|
||||
val value = if (initValue > 0) initValue else error("Provided value $initValue isn't natural")
|
||||
|
||||
override fun toString(): String {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
interface Element {
|
||||
fun render(builder: StringBuilder, indent: String)
|
||||
}
|
||||
|
||||
class TextElement(val text: String) : Element {
|
||||
override fun render(builder: StringBuilder, indent: String) {
|
||||
builder.append("$indent$text\n")
|
||||
}
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
annotation class HtmlTagMarker
|
||||
|
||||
@HtmlTagMarker
|
||||
abstract class Tag(val name: String) : Element {
|
||||
val children = arrayListOf<Element>()
|
||||
val attributes = hashMapOf<String, String>()
|
||||
|
||||
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
|
||||
tag.init()
|
||||
children.add(tag)
|
||||
return tag
|
||||
}
|
||||
|
||||
override fun render(builder: StringBuilder, indent: String) {
|
||||
builder.append("$indent<$name${renderAttributes()}>\n")
|
||||
for (c in children) {
|
||||
c.render(builder, indent + " ")
|
||||
}
|
||||
builder.append("$indent</$name>\n")
|
||||
}
|
||||
|
||||
private fun renderAttributes(): String =
|
||||
attributes.map { (attr, value) ->
|
||||
"$attr=\"$value\""
|
||||
}.joinToString(separator = " ", prefix = " ")
|
||||
|
||||
override fun toString(): String {
|
||||
val builder = StringBuilder()
|
||||
render(builder, "")
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TagWithText(name: String) : Tag(name) {
|
||||
operator fun String.unaryPlus() {
|
||||
children.add(TextElement(this))
|
||||
}
|
||||
}
|
||||
|
||||
class HTML : TagWithText("html") {
|
||||
fun head(init: Head.() -> Unit) = initTag(Head(), init)
|
||||
fun body(init: Body.() -> Unit) = initTag(Body(), init)
|
||||
}
|
||||
|
||||
class Head : TagWithText("head") {
|
||||
fun title(init: Title.() -> Unit) = initTag(Title(), init)
|
||||
fun link(init: Link.() -> Unit) = initTag(Link(), init)
|
||||
fun script(init: Script.() -> Unit) = initTag(Script(), init)
|
||||
}
|
||||
|
||||
class Title : TagWithText("title")
|
||||
class Link : TagWithText("link")
|
||||
class Script : TagWithText("script")
|
||||
|
||||
abstract class BodyTag(name: String) : TagWithText(name) {
|
||||
fun b(init: B.() -> Unit) = initTag(B(), init)
|
||||
fun p(init: P.() -> Unit) = initTag(P(), init)
|
||||
fun h1(init: H1.() -> Unit) = initTag(H1(), init)
|
||||
fun h2(init: H2.() -> Unit) = initTag(H2(), init)
|
||||
fun h4(init: H4.() -> Unit) = initTag(H4(), init)
|
||||
fun hr(init: HR.() -> Unit) = initTag(HR(), init)
|
||||
fun a(href: String, init: A.() -> Unit) {
|
||||
val a = initTag(A(), init)
|
||||
a.href = href
|
||||
}
|
||||
|
||||
fun img(src: String, init: Image.() -> Unit) {
|
||||
val element = initTag(Image(), init)
|
||||
element.src = src
|
||||
}
|
||||
|
||||
fun table(init: Table.() -> Unit) = initTag(Table(), init)
|
||||
fun div(classAttr: String, init: Div.() -> Unit) = initTag(Div(classAttr), init)
|
||||
fun button(classAttr: String, init: Button.() -> Unit) = initTag(Button(classAttr), init)
|
||||
fun header(classAttr: String, init: Header.() -> Unit) = initTag(Header(classAttr), init)
|
||||
fun span(classAttr: String, init: Span.() -> Unit) = initTag(Span(classAttr), init)
|
||||
}
|
||||
|
||||
abstract class BodyTagWithClass(name: String, val classAttr: String) : BodyTag(name) {
|
||||
init {
|
||||
attributes["class"] = classAttr
|
||||
}
|
||||
}
|
||||
|
||||
class Body : BodyTag("body")
|
||||
class B : BodyTag("b")
|
||||
class P : BodyTag("p")
|
||||
class H1 : BodyTag("h1")
|
||||
class H2 : BodyTag("h2")
|
||||
class H4 : BodyTag("h4")
|
||||
class HR : BodyTag("hr")
|
||||
class Div(classAttr: String) : BodyTagWithClass("div", classAttr)
|
||||
class Header(classAttr: String) : BodyTagWithClass("header", classAttr)
|
||||
class Button(classAttr: String) : BodyTagWithClass("button", classAttr)
|
||||
class Span(classAttr: String) : BodyTagWithClass("span", classAttr)
|
||||
|
||||
class A : BodyTag("a") {
|
||||
var href: String by attributes
|
||||
}
|
||||
|
||||
class Image : BodyTag("img") {
|
||||
var src: String by attributes
|
||||
}
|
||||
|
||||
abstract class TableTag(name: String) : BodyTag(name) {
|
||||
fun thead(init: THead.() -> Unit) = initTag(THead(), init)
|
||||
fun tbody(init: TBody.() -> Unit) = initTag(TBody(), init)
|
||||
fun tfoot(init: TFoot.() -> Unit) = initTag(TFoot(), init)
|
||||
}
|
||||
|
||||
abstract class TableBlock(name: String) : TableTag(name) {
|
||||
fun tr(init: TableRow.() -> Unit) = initTag(TableRow(), init)
|
||||
}
|
||||
|
||||
class Table : TableTag("table")
|
||||
class THead : TableBlock("thead")
|
||||
class TFoot : TableBlock("tfoot")
|
||||
class TBody : TableBlock("tbody")
|
||||
|
||||
abstract class TableRowTag(name: String) : TableBlock(name) {
|
||||
var colspan = Natural(1)
|
||||
set(value) {
|
||||
attributes["colspan"] = value.toString()
|
||||
}
|
||||
var rowspan = Natural(1)
|
||||
set(value) {
|
||||
attributes["rowspan"] = value.toString()
|
||||
}
|
||||
|
||||
fun th(rowspan: Natural = Natural(1), colspan: Natural = Natural(1), init: TableHeadInfo.() -> Unit) {
|
||||
val element = initTag(TableHeadInfo(), init)
|
||||
element.rowspan = rowspan
|
||||
element.colspan = colspan
|
||||
}
|
||||
|
||||
fun td(rowspan: Natural = Natural(1), colspan: Natural = Natural(1), init: TableDataInfo.() -> Unit) {
|
||||
val element = initTag(TableDataInfo(), init)
|
||||
element.rowspan = rowspan
|
||||
element.colspan = colspan
|
||||
}
|
||||
}
|
||||
|
||||
class TableRow : TableRowTag("tr")
|
||||
class TableHeadInfo : TableRowTag("th")
|
||||
class TableDataInfo : TableRowTag("td")
|
||||
|
||||
fun html(init: HTML.() -> Unit): HTML {
|
||||
val html = HTML()
|
||||
html.init()
|
||||
return html
|
||||
}
|
||||
|
||||
// Report render to html format.
|
||||
class HTMLRender: Render() {
|
||||
override val name: String
|
||||
get() = "html"
|
||||
|
||||
override fun render (report: SummaryBenchmarksReport, onlyChanges: Boolean) =
|
||||
html {
|
||||
head {
|
||||
title { +"Benchmarks report" }
|
||||
|
||||
// Links to bootstrap files.
|
||||
link {
|
||||
attributes["href"] = "https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
|
||||
attributes["rel"] = "stylesheet"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
|
||||
}
|
||||
script {
|
||||
attributes["src"] = "https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
header("navbar navbar-expand navbar-dark flex-column flex-md-row bd-navbar") {
|
||||
attributes["style"] = "background-color:#161616;"
|
||||
img("https://dashboard.snapcraft.io/site_media/appmedia/2018/04/256px-kotlin-logo-svg.png") {
|
||||
attributes["style"] = "width:60px;height:60px;"
|
||||
}
|
||||
|
||||
span("navbar-brand mb-0 h1") { +"Benchmarks report" }
|
||||
}
|
||||
div("container-fluid") {
|
||||
p{}
|
||||
renderEnvironmentTable(report.environments)
|
||||
renderCompilerTable(report.compilers)
|
||||
hr {}
|
||||
renderStatusSummary(report)
|
||||
hr {}
|
||||
renderPerformanceSummary(report)
|
||||
renderPerformanceDetails(report, onlyChanges)
|
||||
}
|
||||
}
|
||||
}.toString()
|
||||
|
||||
private fun TableRowTag.formatComparedTableData(data: String, compareToData: String?) {
|
||||
td {
|
||||
compareToData?. let {
|
||||
// Highlight changed data.
|
||||
if (it != data)
|
||||
attributes["bgcolor"] = "yellow"
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
+"-"
|
||||
} else {
|
||||
+data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderEnvironment(environment: Environment, name: String, compareTo: Environment? = null) {
|
||||
tbody {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
formatComparedTableData(environment.machine.os, compareTo?.machine?.os)
|
||||
formatComparedTableData(environment.machine.cpu, compareTo?.machine?.cpu)
|
||||
formatComparedTableData(environment.jdk.version, compareTo?.jdk?.version)
|
||||
formatComparedTableData(environment.jdk.vendor, compareTo?.jdk?.vendor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderEnvironmentTable(environments: Pair<Environment, Environment?>) {
|
||||
h4 { +"Environment" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-bordered table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
val firstEnvironment = environments.first
|
||||
val secondEnvironment = environments.second
|
||||
// Table header.
|
||||
thead {
|
||||
tr {
|
||||
th(rowspan = Natural(2)) { +"Run" }
|
||||
th(colspan = Natural(2)) { +"Machine" }
|
||||
th(colspan = Natural(2)) { +"JDK" }
|
||||
}
|
||||
tr {
|
||||
th { + "OS" }
|
||||
th { + "CPU" }
|
||||
th { + "Version"}
|
||||
th { + "Vendor"}
|
||||
}
|
||||
}
|
||||
renderEnvironment(firstEnvironment, "First")
|
||||
secondEnvironment?. let { renderEnvironment(it, "Second", firstEnvironment) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderCompiler(compiler: Compiler, name: String, compareTo: Compiler? = null) {
|
||||
tbody {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
formatComparedTableData(compiler.backend.type.type, compareTo?.backend?.type?.type)
|
||||
formatComparedTableData(compiler.backend.version, compareTo?.backend?.version)
|
||||
formatComparedTableData(compiler.backend.flags.joinToString(), compareTo?.backend?.flags?.joinToString())
|
||||
formatComparedTableData(compiler.kotlinVersion, compareTo?.kotlinVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderCompilerTable(compilers: Pair<Compiler, Compiler?>) {
|
||||
h4 { +"Compiler" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-bordered table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
val firstCompiler = compilers.first
|
||||
val secondCompiler = compilers.second
|
||||
|
||||
// Table header.
|
||||
thead {
|
||||
tr {
|
||||
th(rowspan = Natural(2)) { +"Run" }
|
||||
th(colspan = Natural(3)) { +"Backend" }
|
||||
th(rowspan = Natural(2)) { +"Kotlin" }
|
||||
}
|
||||
tr {
|
||||
th { + "Type" }
|
||||
th { + "Version" }
|
||||
th { + "Flags"}
|
||||
}
|
||||
}
|
||||
renderCompiler(firstCompiler, "First")
|
||||
secondCompiler?. let { renderCompiler(it, "Second", firstCompiler) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableBlock.renderBucketInfo(bucket: Collection<Any>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
tr {
|
||||
th {
|
||||
attributes["scope"] = "row"
|
||||
+name
|
||||
}
|
||||
td {
|
||||
+"${bucket.size}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderCollapsedData(name: String, isCollapsed: Boolean = false, colorStyle: String = "",
|
||||
init: BodyTag.() -> Unit) {
|
||||
val show = if (!isCollapsed) "show" else ""
|
||||
val tagName = name.replace(' ', '_')
|
||||
div("accordion") {
|
||||
div("card") {
|
||||
attributes["style"] = "border-bottom: 1px solid rgba(0,0,0,.125);"
|
||||
div("card-header") {
|
||||
attributes["id"] = "heading"
|
||||
attributes["style"] = "padding: 0;$colorStyle"
|
||||
button("btn btn-link") {
|
||||
attributes["data-toggle"] = "collapse"
|
||||
attributes["data-target"] = "#$tagName"
|
||||
+name
|
||||
}
|
||||
}
|
||||
|
||||
div("collapse $show") {
|
||||
attributes["id"] = tagName
|
||||
div("accordion-inner") {
|
||||
init()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableTag.renderTableFromList(list: List<String>, name: String) {
|
||||
if (!list.isEmpty()) {
|
||||
thead {
|
||||
tr {
|
||||
th { +name }
|
||||
}
|
||||
}
|
||||
list.forEach {
|
||||
tbody {
|
||||
tr {
|
||||
td { +it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderStatusSummary(report: SummaryBenchmarksReport) {
|
||||
h4 { +"Status Summary" }
|
||||
val failedBenchmarks = report.failedBenchmarks
|
||||
if (failedBenchmarks.isEmpty()) {
|
||||
div("alert alert-success") {
|
||||
attributes["role"] = "alert"
|
||||
+"All benchmarks passed!"
|
||||
}
|
||||
} else {
|
||||
div("alert alert-danger") {
|
||||
attributes["role"] = "alert"
|
||||
+"There are failed benchmarks!"
|
||||
}
|
||||
}
|
||||
|
||||
val benchmarksWithChangedStatus = report.getBenchmarksWithChangedStatus()
|
||||
val newFailures = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }
|
||||
val newPasses = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }
|
||||
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Status Group" }
|
||||
th { +"#" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
renderBucketInfo(failedBenchmarks, "Failed (total)")
|
||||
renderBucketInfo(newFailures, "New Failures")
|
||||
renderBucketInfo(newPasses, "New Passes")
|
||||
renderBucketInfo(report.addedBenchmarks, "Added")
|
||||
renderBucketInfo(report.removedBenchmarks, "Removed")
|
||||
}
|
||||
tfoot {
|
||||
tr {
|
||||
th { +"Total becnhmarks number" }
|
||||
th { +"${report.benchmarksNumber}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!failedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Failures", colorStyle = "background-color: lightpink") {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
val newFailuresList = newFailures.map { it.field }
|
||||
renderTableFromList(newFailuresList, "New Failures")
|
||||
|
||||
val existingFailures = failedBenchmarks.filter { it !in newFailuresList }
|
||||
renderTableFromList(existingFailures, "Existing Failures")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!newPasses.isEmpty()) {
|
||||
renderCollapsedData("New Passes", colorStyle = "background-color: lightgreen") {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(newPasses.map { it.field }, "New Passes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!report.addedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Added", true) {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(report.addedBenchmarks, "Added benchmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!report.removedBenchmarks.isEmpty()) {
|
||||
renderCollapsedData("Removed", true) {
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial; font-size: 11pt;"
|
||||
renderTableFromList(report.removedBenchmarks, "Removed benchmarks")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderPerformanceSummary(report: SummaryBenchmarksReport) {
|
||||
if (!report.improvements.isEmpty() || !report.regressions.isEmpty()) {
|
||||
h4 { +"Performance Summary" }
|
||||
table {
|
||||
attributes["class"] = "table table-sm table-striped table-hover"
|
||||
attributes["style"] = "width:initial;"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Change" }
|
||||
th { +"#" }
|
||||
th { +"Maximum" }
|
||||
th { +"Geometric mean" }
|
||||
}
|
||||
}
|
||||
val maximumRegression = report.maximumRegression
|
||||
val maximumImprovement = report.maximumImprovement
|
||||
val regressionsGeometricMean = report.regressionsGeometricMean
|
||||
val improvementsGeometricMean = report.improvementsGeometricMean
|
||||
tbody {
|
||||
if (!report.regressions.isEmpty()) {
|
||||
tr {
|
||||
th { +"Regressions" }
|
||||
td { +"${report.regressions.size}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
maximumRegression/maxOf(maximumRegression, abs(maximumImprovement)))
|
||||
.backgroundStyle
|
||||
+formatValue(maximumRegression, true)
|
||||
}
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
regressionsGeometricMean/maxOf(regressionsGeometricMean,
|
||||
abs(improvementsGeometricMean)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.regressionsGeometricMean, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!report.improvements.isEmpty()) {
|
||||
tr {
|
||||
th { +"Improvements" }
|
||||
td { +"${report.improvements.size}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
maximumImprovement/maxOf(maximumRegression, abs(maximumImprovement)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.maximumImprovement, true)
|
||||
}
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(
|
||||
improvementsGeometricMean/maxOf(regressionsGeometricMean,
|
||||
abs(improvementsGeometricMean)))
|
||||
.backgroundStyle
|
||||
+formatValue(report.improvementsGeometricMean, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableBlock.renderBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
||||
bucket: Map<String, ScoreChange>? = null, rowStyle: String? = null) {
|
||||
if (bucket != null && !bucket.isEmpty()) {
|
||||
// Find max ratio.
|
||||
val maxRatio = bucket.values.map { it.second.mean }.maxOrNull()!!
|
||||
// There are changes in performance.
|
||||
// Output changed benchmarks.
|
||||
for ((name, change) in bucket) {
|
||||
tr {
|
||||
rowStyle?. let {
|
||||
attributes["style"] = rowStyle
|
||||
}
|
||||
th { +name }
|
||||
td { +"${fullSet.getValue(name).first?.description}" }
|
||||
td { +"${fullSet.getValue(name).second?.description}" }
|
||||
td {
|
||||
attributes["bgcolor"] = ColoredCell(if (bucket.values.first().first.mean == 0.0) null
|
||||
else change.first.mean / abs(bucket.values.first().first.mean))
|
||||
.backgroundStyle
|
||||
+"${change.first.description + " %"}"
|
||||
}
|
||||
td {
|
||||
val scaledRatio = if (maxRatio == 0.0) null else change.second.mean / maxRatio
|
||||
attributes["bgcolor"] = ColoredCell(scaledRatio,
|
||||
borderPositive = { cellValue -> cellValue > 1.0 / maxRatio }).backgroundStyle
|
||||
+"${change.second.description}"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bucket == null) {
|
||||
// Output all values without performance changes.
|
||||
val placeholder = "-"
|
||||
for ((name, value) in fullSet) {
|
||||
tr {
|
||||
th { +name }
|
||||
td { +"${value.first?.description ?: placeholder}" }
|
||||
td { +"${value.second?.description ?: placeholder}" }
|
||||
td { +placeholder }
|
||||
td { +placeholder }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BodyTag.renderPerformanceDetails(report: SummaryBenchmarksReport, onlyChanges: Boolean) {
|
||||
if (onlyChanges) {
|
||||
if (report.regressions.isEmpty() && report.improvements.isEmpty()) {
|
||||
div("alert alert-success") {
|
||||
attributes["role"] = "alert"
|
||||
+"All becnhmarks are stable!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
attributes["id"] = "result"
|
||||
attributes["class"] = "table table-striped table-bordered"
|
||||
thead {
|
||||
tr {
|
||||
th { +"Benchmark" }
|
||||
th { +"First score" }
|
||||
th { +"Second score" }
|
||||
th { +"Percent" }
|
||||
th { +"Ratio" }
|
||||
}
|
||||
}
|
||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||
|
||||
tbody {
|
||||
renderBenchmarksDetails(
|
||||
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||
geoMeanChangeMap, "border-bottom: 2.3pt solid black; border-top: 2.3pt solid black")
|
||||
renderBenchmarksDetails(report.mergedReport, report.regressions)
|
||||
renderBenchmarksDetails(report.mergedReport, report.improvements)
|
||||
if (!onlyChanges) {
|
||||
// Print all remaining results.
|
||||
renderBenchmarksDetails(report.mergedReport.filter { it.key !in report.regressions.keys &&
|
||||
it.key !in report.improvements.keys })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Color(val red: Double, val green: Double, val blue: Double) {
|
||||
operator fun times(coefficient: Double) =
|
||||
Color(red * coefficient, green * coefficient, blue * coefficient)
|
||||
|
||||
operator fun plus(other: Color) =
|
||||
Color(red + other.red, green + other.green, blue + other.blue)
|
||||
|
||||
override fun toString() =
|
||||
"#" + buildString {
|
||||
listOf(red, green, blue).forEach {
|
||||
append(clamp((it * 255).toInt(), 0, 255).toString(16).padStart(2, '0'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ColoredCell(val scaledValue: Double?, val reverse: Boolean = false,
|
||||
val borderPositive: (cellValue: Double) -> Boolean = { cellValue -> cellValue > 0 }) {
|
||||
val value: Double
|
||||
val neutralColor = Color(1.0,1.0 , 1.0)
|
||||
val negativeColor = Color(0.0, 1.0, 0.0)
|
||||
val positiveColor = Color(1.0, 0.0, 0.0)
|
||||
|
||||
init {
|
||||
value = scaledValue?.let { if (abs(scaledValue) <= 1.0) scaledValue else error ("Value should be scaled in range [-1.0; 1.0]") }
|
||||
?: 0.0
|
||||
}
|
||||
|
||||
val backgroundStyle: String
|
||||
get() = scaledValue?.let { getColor().toString() } ?: ""
|
||||
|
||||
fun getColor(): Color {
|
||||
val currentValue = clamp(value, -1.0, 1.0)
|
||||
val cellValue = if (reverse) -currentValue else currentValue
|
||||
val baseColor = if (borderPositive(cellValue)) positiveColor else negativeColor
|
||||
// Smooth mapping to put first 20% of change into 50% of range,
|
||||
// although really we should compensate for luma.
|
||||
val color = sin((abs(cellValue).pow(.477)) * kotlin.math.PI * .5)
|
||||
return linearInterpolation(neutralColor, baseColor, color)
|
||||
}
|
||||
|
||||
private fun linearInterpolation(a: Color, b: Color, coefficient: Double): Color {
|
||||
val reversedCoefficient = 1.0 - coefficient
|
||||
return a * reversedCoefficient + b * coefficient
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
// Report render to text format.
|
||||
class JsonResultsRender: Render() {
|
||||
override val name: String
|
||||
get() = "json"
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean) =
|
||||
report.getBenchmarksReport().toJson()
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
|
||||
// Report render to text format.
|
||||
class MetricResultsRender: Render() {
|
||||
override val name: String
|
||||
get() = "metrics"
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val results = report.mergedReport.map { entry ->
|
||||
buildString {
|
||||
val metric = entry.value.first!!.metric
|
||||
append("{ \"benchmarkName\": \"${entry.key.removeSuffix(metric.suffix)}\",")
|
||||
append("\"metric\": \"${metric}\",")
|
||||
append("\"value\": \"${entry.value.first!!.score}\" }")
|
||||
}
|
||||
}.joinToString(", ")
|
||||
return "[ $results ]"
|
||||
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
// Base class for printing report in different formats.
|
||||
abstract class Render {
|
||||
|
||||
companion object {
|
||||
fun getRenderByName(name: String) =
|
||||
when (name) {
|
||||
"text" -> TextRender()
|
||||
"html" -> HTMLRender()
|
||||
"teamcity" -> TeamCityStatisticsRender()
|
||||
"statistics" -> StatisticsRender()
|
||||
"metrics" -> MetricResultsRender()
|
||||
else -> error("Unknown render $name")
|
||||
}
|
||||
}
|
||||
|
||||
abstract val name: String
|
||||
|
||||
abstract fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean = false): String
|
||||
|
||||
// Print report using render.
|
||||
fun print(report: SummaryBenchmarksReport, onlyChanges: Boolean = false, outputFile: String? = null) {
|
||||
val content = render(report, onlyChanges)
|
||||
outputFile?.let {
|
||||
writeToFile(outputFile, content)
|
||||
} ?: println(content)
|
||||
}
|
||||
|
||||
protected fun formatValue(number: Double, isPercent: Boolean = false): String =
|
||||
if (isPercent) number.format(2) + "%" else number.format()
|
||||
}
|
||||
|
||||
// Report render to text format.
|
||||
class TextRender: Render() {
|
||||
override val name: String
|
||||
get() = "text"
|
||||
|
||||
private val content = StringBuilder()
|
||||
private val headerSeparator = "================="
|
||||
private val wideColumnWidth = 50
|
||||
private val standardColumnWidth = 25
|
||||
|
||||
private fun append(text: String = "") {
|
||||
content.append("$text\n")
|
||||
}
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
renderEnvChanges(report.envChanges, "Environment")
|
||||
renderEnvChanges(report.kotlinChanges, "Compiler")
|
||||
renderStatusSummary(report)
|
||||
renderStatusChangesDetails(report.getBenchmarksWithChangedStatus())
|
||||
renderPerformanceSummary(report)
|
||||
renderPerformanceDetails(report, onlyChanges)
|
||||
return content.toString()
|
||||
}
|
||||
|
||||
private fun printBucketInfo(bucket: Collection<Any>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
append("$name: ${bucket.size}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> printStatusChangeInfo(bucket: List<FieldChange<T>>, name: String) {
|
||||
if (!bucket.isEmpty()) {
|
||||
append("$name:")
|
||||
for (change in bucket) {
|
||||
append(change.renderAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun renderEnvChanges(envChanges: List<FieldChange<String>>, bucketName: String) {
|
||||
if (!envChanges.isEmpty()) {
|
||||
append(ChangeReport(bucketName, envChanges).renderAsTextReport())
|
||||
}
|
||||
}
|
||||
|
||||
fun renderStatusChangesDetails(benchmarksWithChangedStatus: List<FieldChange<BenchmarkResult.Status>>) {
|
||||
if (!benchmarksWithChangedStatus.isEmpty()) {
|
||||
append("Changes in status")
|
||||
append(headerSeparator)
|
||||
printStatusChangeInfo(benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }, "New failures")
|
||||
printStatusChangeInfo(benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }, "New passes")
|
||||
append()
|
||||
}
|
||||
}
|
||||
|
||||
fun renderStatusSummary(report: SummaryBenchmarksReport) {
|
||||
append("Status summary")
|
||||
append(headerSeparator)
|
||||
|
||||
val failedBenchmarks = report.failedBenchmarks
|
||||
val addedBenchmarks = report.addedBenchmarks
|
||||
val removedBenchmarks = report.removedBenchmarks
|
||||
if (failedBenchmarks.isEmpty()) {
|
||||
append("All benchmarks passed!")
|
||||
}
|
||||
if (!failedBenchmarks.isEmpty() || !addedBenchmarks.isEmpty() || !removedBenchmarks.isEmpty()) {
|
||||
printBucketInfo(failedBenchmarks, "Failed benchmarks")
|
||||
printBucketInfo(addedBenchmarks, "Added benchmarks")
|
||||
printBucketInfo(removedBenchmarks, "Removed benchmarks")
|
||||
}
|
||||
append("Total becnhmarks number: ${report.benchmarksNumber}")
|
||||
append()
|
||||
}
|
||||
|
||||
fun renderPerformanceSummary(report: SummaryBenchmarksReport) {
|
||||
if (!report.regressions.isEmpty() || !report.improvements.isEmpty()) {
|
||||
append("Performance summary")
|
||||
append(headerSeparator)
|
||||
if (!report.regressions.isEmpty()) {
|
||||
append("Regressions: Maximum = ${formatValue(report.maximumRegression, true)}," +
|
||||
" Geometric mean = ${formatValue(report.regressionsGeometricMean, true)}")
|
||||
}
|
||||
if (!report.improvements.isEmpty()) {
|
||||
append("Improvements: Maximum = ${formatValue(report.maximumImprovement, true)}," +
|
||||
" Geometric mean = ${formatValue(report.improvementsGeometricMean, true)}")
|
||||
}
|
||||
append()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatColumn(content:String, isWide: Boolean = false): String =
|
||||
content.padEnd(if (isWide) wideColumnWidth else standardColumnWidth, ' ')
|
||||
|
||||
private fun printBenchmarksDetails(fullSet: Map<String, SummaryBenchmark>,
|
||||
bucket: Map<String, ScoreChange>? = null) {
|
||||
val placeholder = "-"
|
||||
if (bucket != null) {
|
||||
// There are changes in performance.
|
||||
// Output changed benchmarks.
|
||||
for ((name, change) in bucket) {
|
||||
append(formatColumn(name, true) +
|
||||
formatColumn(fullSet.getValue(name).first?.description ?: placeholder) +
|
||||
formatColumn(fullSet.getValue(name).second?.description ?: placeholder) +
|
||||
formatColumn(change.first.description + " %") +
|
||||
formatColumn(change.second.description))
|
||||
}
|
||||
} else {
|
||||
// Output all values without performance changes.
|
||||
for ((name, value) in fullSet) {
|
||||
append(formatColumn(name, true) +
|
||||
formatColumn(value.first?.description ?: placeholder) +
|
||||
formatColumn(value.second?.description ?: placeholder) +
|
||||
formatColumn(placeholder) +
|
||||
formatColumn(placeholder))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printTableLineSeparator(tableWidth: Int) =
|
||||
append("${"-".padEnd(tableWidth, '-')}")
|
||||
|
||||
private fun printPerformanceTableHeader(): Int {
|
||||
val wideColumns = listOf(formatColumn("Benchmark", true))
|
||||
val standardColumns = listOf(formatColumn("First score"),
|
||||
formatColumn("Second score"),
|
||||
formatColumn("Percent"),
|
||||
formatColumn("Ratio"))
|
||||
val tableWidth = wideColumnWidth * wideColumns.size + standardColumnWidth * standardColumns.size
|
||||
append("${wideColumns.joinToString(separator = "")}${standardColumns.joinToString(separator = "")}")
|
||||
printTableLineSeparator(tableWidth)
|
||||
return tableWidth
|
||||
}
|
||||
|
||||
fun renderPerformanceDetails(report: SummaryBenchmarksReport, onlyChanges: Boolean = false) {
|
||||
append("Performance details")
|
||||
append(headerSeparator)
|
||||
|
||||
if (onlyChanges) {
|
||||
if (report.regressions.isEmpty() && report.improvements.isEmpty()) {
|
||||
append("All becnhmarks are stable.")
|
||||
}
|
||||
}
|
||||
|
||||
val tableWidth = printPerformanceTableHeader()
|
||||
// Print geometric mean.
|
||||
val geoMeanChangeMap = report.geoMeanScoreChange?.
|
||||
let { mapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanScoreChange!!) }
|
||||
printBenchmarksDetails(
|
||||
mutableMapOf(report.geoMeanBenchmark.first!!.name to report.geoMeanBenchmark),
|
||||
geoMeanChangeMap)
|
||||
printTableLineSeparator(tableWidth)
|
||||
printBenchmarksDetails(report.mergedReport, report.regressions)
|
||||
printBenchmarksDetails(report.mergedReport, report.improvements)
|
||||
if (!onlyChanges) {
|
||||
// Print all remaining results.
|
||||
printBenchmarksDetails(report.mergedReport.filter { it.key !in report.regressions.keys &&
|
||||
it.key !in report.improvements.keys })
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
|
||||
enum class Status {
|
||||
FAILED, FIXED, IMPROVED, REGRESSED, STABLE, UNSTABLE
|
||||
}
|
||||
|
||||
// Report render to short summary statistics.
|
||||
class StatisticsRender: Render() {
|
||||
override val name: String
|
||||
get() = "statistics"
|
||||
|
||||
private var content = StringBuilder()
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val benchmarksWithChangedStatus = report.getBenchmarksWithChangedStatus()
|
||||
val newPasses = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.PASSED }
|
||||
val newFailures = benchmarksWithChangedStatus
|
||||
.filter { it.current == BenchmarkResult.Status.FAILED }
|
||||
if (report.failedBenchmarks.isNotEmpty()) {
|
||||
content.append("failed: ${report.failedBenchmarks.size}\n")
|
||||
}
|
||||
val status = when {
|
||||
newFailures.isNotEmpty() -> {
|
||||
content.append("new failures: ${newFailures.size}\n")
|
||||
Status.FAILED
|
||||
}
|
||||
newPasses.isNotEmpty() -> {
|
||||
content.append("new passes: ${newPasses.size}\n")
|
||||
Status.FIXED
|
||||
}
|
||||
report.improvements.isNotEmpty() && report.regressions.isNotEmpty() -> {
|
||||
content.append("regressions: ${report.regressions.size}\nimprovements: ${report.improvements.size}")
|
||||
Status.UNSTABLE
|
||||
}
|
||||
report.improvements.isNotEmpty() && report.regressions.isEmpty() -> {
|
||||
content.append("improvements: ${report.improvements.size}")
|
||||
Status.IMPROVED
|
||||
}
|
||||
report.improvements.isEmpty() && report.regressions.isNotEmpty() -> {
|
||||
content.append("regressions: ${report.regressions.size}")
|
||||
Status.REGRESSED
|
||||
}
|
||||
else -> Status.STABLE
|
||||
}
|
||||
return """
|
||||
status: $status
|
||||
total: ${report.benchmarksNumber}
|
||||
""".trimIndent() + "\n$content"
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.renders
|
||||
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
|
||||
// Report render to text format.
|
||||
class TeamCityStatisticsRender: Render() {
|
||||
override val name: String
|
||||
get() = "teamcity"
|
||||
|
||||
private var content = StringBuilder()
|
||||
|
||||
override fun render(report: SummaryBenchmarksReport, onlyChanges: Boolean): String {
|
||||
val currentDurations = report.currentBenchmarksDuration
|
||||
|
||||
content.append("##teamcity[testSuiteStarted name='Benchmarks']\n")
|
||||
|
||||
// For current benchmarks print score as TeamCity Test Metadata
|
||||
report.currentMeanVarianceBenchmarks.forEach { benchmark ->
|
||||
renderBenchmark(benchmark, currentDurations[benchmark.name]!!)
|
||||
renderSummaryBecnhmarkValue(benchmark)
|
||||
}
|
||||
content.append("##teamcity[testSuiteFinished name='Benchmarks']\n")
|
||||
|
||||
// Report geometric mean as build statistic value
|
||||
renderGeometricMean(report.geoMeanBenchmark.first!!)
|
||||
|
||||
return content.toString()
|
||||
}
|
||||
|
||||
private fun renderSummaryBecnhmarkValue(benchmark: MeanVarianceBenchmark) {
|
||||
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='Mean'" +
|
||||
" type='number' value='${benchmark.score}']\n")
|
||||
content.append("##teamcity[testMetadata testName='${benchmark.name}' name='Variance'" +
|
||||
" type='number' value='${benchmark.variance}']\n")
|
||||
}
|
||||
|
||||
// Produce benchmark as test in TeamCity
|
||||
private fun renderBenchmark(benchmark: BenchmarkResult , duration: Double) {
|
||||
content.append("##teamcity[testStarted name='${benchmark.name}']\n")
|
||||
if (benchmark.status == BenchmarkResult.Status.FAILED) {
|
||||
content.append("##teamcity[testFailed name='${benchmark.name}']\n")
|
||||
}
|
||||
// test_duration_in_milliseconds is set for TeamCity
|
||||
content.append("##teamcity[testFinished name='${benchmark.name}' duration='${(duration / 1000).toInt()}']\n")
|
||||
}
|
||||
|
||||
private fun renderGeometricMean(geoMeanBenchmark: MeanVarianceBenchmark) {
|
||||
content.append("##teamcity[buildStatisticValue key='Geometric mean' value='${geoMeanBenchmark.score}']\n")
|
||||
content.append("##teamcity[buildStatisticValue key='Geometric mean variance' value='${geoMeanBenchmark.variance}']\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
headers = curl/curl.h
|
||||
headerFilter = curl/*
|
||||
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lcurl
|
||||
linkerOpts.mingw = -lcurl
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import kotlin.test.*
|
||||
import kotlin.math.abs
|
||||
import org.jetbrains.report.BenchmarkResult
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
|
||||
class AnalyzerTests {
|
||||
private val eps = 0.000001
|
||||
|
||||
private fun createMeanVarianceBenchmarks(): Pair<MeanVarianceBenchmark, MeanVarianceBenchmark> {
|
||||
val first = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 9.0, BenchmarkResult.Metric.EXECUTION_TIME, 9.0, 10, 10, 0.0001)
|
||||
val second = MeanVarianceBenchmark("testBenchmark", BenchmarkResult.Status.PASSED, 10.0, BenchmarkResult.Metric.EXECUTION_TIME, 10.0, 10, 10, 0.0001)
|
||||
|
||||
return Pair(first, second)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeoMean() {
|
||||
val numbers = listOf(4.0, 6.0, 9.0)
|
||||
val value = geometricMean(numbers)
|
||||
val expected = 6.0
|
||||
assertTrue(abs(value - expected) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testComputeMeanVariance() {
|
||||
val numbers = listOf(10.1, 10.2, 10.3)
|
||||
val value = computeMeanVariance(numbers)
|
||||
val expectedMean = 10.2
|
||||
val expectedVariance = 0.07872455
|
||||
assertTrue(abs(value.mean - expectedMean) < eps)
|
||||
assertTrue(abs(value.variance - expectedVariance) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calcPercentageDiff() {
|
||||
val inputs = createMeanVarianceBenchmarks()
|
||||
|
||||
val percent = inputs.first.calcPercentageDiff(inputs.second)
|
||||
val expectedMean = -9.99809998
|
||||
val expectedVariance = 0.0021
|
||||
assertTrue(abs(percent.mean - expectedMean) < eps)
|
||||
//assertTrue(abs(percent.variance - expectedVariance) < eps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun calcRatio() {
|
||||
val inputs = createMeanVarianceBenchmarks()
|
||||
|
||||
val ratio = inputs.first.calcRatio(inputs.second)
|
||||
val expectedMean = 0.9
|
||||
val expectedVariance = 0.00001899
|
||||
|
||||
assertTrue(abs(ratio.mean - expectedMean) < eps)
|
||||
assertTrue(abs(ratio.variance - expectedVariance) < eps)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.konan.*
|
||||
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../../')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://cache-redirector.jetbrains.com/plugins.gradle.org/m2/"
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.4'
|
||||
classpath "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java-gradle-plugin'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
group = 'org.jetbrains.kotlin'
|
||||
version = CompilerVersionGeneratedKt.getCurrentCompilerVersion()
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
bundleDependencies {
|
||||
transitive = false
|
||||
}
|
||||
|
||||
implementation.extendsFrom shadow
|
||||
compileOnly.extendsFrom bundleDependencies
|
||||
testImplementation.extendsFrom bundleDependencies
|
||||
}
|
||||
|
||||
dependencies {
|
||||
shadow "org.jetbrains.kotlin:kotlin-stdlib:1.3.0"
|
||||
|
||||
// Bundle the serialization plugin into the final jar because we shade classes of the kotlin plugin
|
||||
// while the serialization one extends them.
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-native-utils:$kotlinVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-util-io:$kotlinVersion"
|
||||
bundleDependencies "org.jetbrains.kotlin:kotlin-util-klib:$kotlinVersion"
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$buildKotlinVersion"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$buildKotlinVersion"
|
||||
testImplementation "org.tools4j:tools4j-spockito:1.6"
|
||||
testImplementation('org.spockframework:spock-core:1.1-groovy-2.4') {
|
||||
exclude module: 'groovy-all'
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
from sourceSets.main.output
|
||||
configurations = [project.configurations.bundleDependencies]
|
||||
archiveClassifier.set(null)
|
||||
relocate('org.jetbrains.kotlinx', 'shadow.org.jetbrains.kotlinx')
|
||||
relocate('org.jetbrains.kotlin.compilerRunner', 'shadow.org.jetbrains.kotlin.compilerRunner')
|
||||
relocate('org.jetbrains.kotlin.konan', 'shadow.org.jetbrains.kotlin.konan')
|
||||
relocate('org.jetbrains.kotlin.gradle', 'shadow.org.jetbrains.kotlin.gradle') {
|
||||
exclude('org.jetbrains.kotlin.gradle.plugin.experimental.**')
|
||||
exclude('org.jetbrains.kotlin.gradle.plugin.konan.**')
|
||||
exclude('org.jetbrains.kotlin.gradle.plugin.model.**')
|
||||
}
|
||||
|
||||
exclude {
|
||||
def path = it.relativePath.pathString
|
||||
if (path.startsWith("META-INF/gradle-plugins") && path.endsWith(".properties")) {
|
||||
def fileName = it.name
|
||||
def id = fileName.take(fileName.lastIndexOf('.'))
|
||||
return project.gradlePlugin.plugins.findByName(id) == null
|
||||
}
|
||||
return false
|
||||
}
|
||||
exclude('META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar')
|
||||
exclude('META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin')
|
||||
}
|
||||
|
||||
jar {
|
||||
dependsOn shadowJar
|
||||
enabled = false
|
||||
}
|
||||
|
||||
pluginUnderTestMetadata {
|
||||
dependsOn shadowJar
|
||||
doLast {
|
||||
// Since Gradle 4.10 it isn't possible to edit the pluginUnderTest classpath.
|
||||
// So we have to manually set the implementation-classpath to get the output fat-jar.
|
||||
def pluginMetadata = outputDirectory.get().file(PluginUnderTestMetadata.METADATA_FILE_NAME).getAsFile()
|
||||
def classpath = files(shadowJar.archivePath) + configurations.shadow
|
||||
new Properties().with { properties ->
|
||||
pluginMetadata.withInputStream {
|
||||
properties.load(it)
|
||||
}
|
||||
properties.setProperty(PluginUnderTestMetadata.IMPLEMENTATION_CLASSPATH_PROP_KEY , classpath.asPath)
|
||||
pluginMetadata.withOutputStream {
|
||||
properties.store(it, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
dependsOn shadowJar
|
||||
systemProperty("kotlin.version", kotlinVersion)
|
||||
systemProperty("kotlin.repo", kotlinCompilerRepo)
|
||||
if (project.hasProperty("konan.home")) {
|
||||
systemProperty("konan.home", project.property("konan.home"))
|
||||
systemProperty("org.jetbrains.kotlin.native.home", project.property("konan.home"))
|
||||
} else if (project.hasProperty("org.jetbrains.kotlin.native.home")) {
|
||||
systemProperty("org.jetbrains.kotlin.native.home", project.property("org.jetbrains.kotlin.native.home"))
|
||||
} else {
|
||||
// The Koltin/Native compiler must be built before test execution.
|
||||
systemProperty("konan.home", distDir.absolutePath)
|
||||
systemProperty("org.jetbrains.kotlin.native.home", distDir.absolutePath)
|
||||
}
|
||||
if (project.hasProperty("konan.jvmArgs")) {
|
||||
systemProperty("konan.jvmArgs", project.property("konan.jvmArgs"))
|
||||
}
|
||||
// Uncomment for debugging.
|
||||
//testLogging.showStandardStreams = true
|
||||
if (project.hasProperty("maxParallelForks")) {
|
||||
maxParallelForks=project.property("maxParallelForks")
|
||||
}
|
||||
if (project.hasProperty("filter")) {
|
||||
filter.includeTestsMatching project.property("filter")
|
||||
}
|
||||
if (project.hasProperty("gradleVersion")) {
|
||||
systemProperty("gradleVersion", project.property("gradleVersion"))
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
from(file("$rootBuildDirectory/utilities/env_blacklist"))
|
||||
}
|
||||
|
||||
tasks.named('compileTestGroovy') {
|
||||
classpath = sourceSets.test.compileClasspath
|
||||
}
|
||||
|
||||
tasks.named('compileTestKotlin') {
|
||||
classpath += files(sourceSets.test.groovy.classesDirectory)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create('konan') {
|
||||
id = 'konan'
|
||||
implementationClass = 'org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin'
|
||||
}
|
||||
// We bundle a shaded version of kotlinx-serialization plugin
|
||||
create('kotlinx-serialization-native') {
|
||||
id = 'kotlinx-serialization-native'
|
||||
implementationClass = 'shadow.org.jetbrains.kotlinx.serialization.gradle.SerializationGradleSubplugin'
|
||||
}
|
||||
|
||||
create('org.jetbrains.kotlin.konan') {
|
||||
id = 'org.jetbrains.kotlin.konan'
|
||||
implementationClass = 'org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
rootProject.name = "kotlin-native-gradle-plugin"
|
||||
includeBuild '../../shared'
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.experimental.internal
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.internal.component.UsageContext
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
import org.gradle.language.cpp.internal.NativeVariantIdentity
|
||||
import org.gradle.nativeplatform.MachineArchitecture
|
||||
import org.gradle.nativeplatform.OperatingSystemFamily
|
||||
import org.gradle.nativeplatform.TargetMachine
|
||||
import org.gradle.nativeplatform.TargetMachineFactory
|
||||
import org.gradle.nativeplatform.platform.NativePlatform
|
||||
import org.gradle.nativeplatform.platform.internal.*
|
||||
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.util.visibleName
|
||||
|
||||
interface KotlinNativePlatform: NativePlatform {
|
||||
val target: KonanTarget
|
||||
}
|
||||
|
||||
fun KonanTarget.getGradleOS(): OperatingSystemInternal = family.visibleName.let {
|
||||
DefaultOperatingSystem(it, OperatingSystem.forName(it))
|
||||
}
|
||||
|
||||
fun KonanTarget.getGradleOSFamily(objectFactory: ObjectFactory): OperatingSystemFamily {
|
||||
return objectFactory.named(OperatingSystemFamily::class.java, family.visibleName)
|
||||
}
|
||||
|
||||
fun KonanTarget.getGradleCPU(): ArchitectureInternal = architecture.visibleName.let {
|
||||
Architectures.forInput(it)
|
||||
}
|
||||
|
||||
fun KonanTarget.toTargetMachine(objectFactory: ObjectFactory): TargetMachine = object: TargetMachine {
|
||||
override fun getOperatingSystemFamily(): OperatingSystemFamily =
|
||||
getGradleOSFamily(objectFactory)
|
||||
|
||||
override fun getArchitecture(): MachineArchitecture =
|
||||
objectFactory.named(MachineArchitecture::class.java, this@toTargetMachine.architecture.visibleName)
|
||||
}
|
||||
|
||||
class DefaultKotlinNativePlatform(name: String, override val target: KonanTarget):
|
||||
DefaultNativePlatform(name, target.getGradleOS(), target.getGradleCPU()),
|
||||
KotlinNativePlatform
|
||||
{
|
||||
constructor(target: KonanTarget): this(target.visibleName, target)
|
||||
|
||||
// TODO: Extend ImmutableDefaultNativePlatform and get rid of these methods after switch to Gradle 4.8
|
||||
private fun notImplemented(): Nothing = throw NotImplementedError("Not Implemented in Kotlin/Native plugin")
|
||||
override fun operatingSystem(name: String?) = notImplemented()
|
||||
override fun withArchitecture(architecture: ArchitectureInternal?) = notImplemented()
|
||||
override fun architecture(name: String?) = notImplemented()
|
||||
}
|
||||
|
||||
// NativeVariantIdentity constructor was changed in Gradle 5.1
|
||||
// So we have to use reflection to create instance of this class in earlier versions.
|
||||
internal fun compatibleVariantIdentity(
|
||||
project: Project,
|
||||
name: String,
|
||||
baseName: Provider<String>,
|
||||
group: Provider<String>,
|
||||
version: Provider<String>,
|
||||
debuggable: Boolean,
|
||||
optimized: Boolean,
|
||||
target: KonanTarget,
|
||||
linkUsage: UsageContext?,
|
||||
runtimeUsage: UsageContext?
|
||||
): NativeVariantIdentity =
|
||||
if (isGradleVersionAtLeast(5, 1)) {
|
||||
val targetMachineFactory = (project as ProjectInternal).services.get(TargetMachineFactory::class.java)
|
||||
NativeVariantIdentity(
|
||||
name,
|
||||
baseName,
|
||||
group,
|
||||
version,
|
||||
debuggable,
|
||||
optimized,
|
||||
targetMachineFactory.os(target.family.name),
|
||||
linkUsage,
|
||||
runtimeUsage
|
||||
)
|
||||
} else {
|
||||
NativeVariantIdentity::class.java.getConstructor(
|
||||
String::class.java,
|
||||
Provider::class.java,
|
||||
Provider::class.java,
|
||||
Provider::class.java,
|
||||
Boolean::class.javaPrimitiveType,
|
||||
Boolean::class.javaPrimitiveType,
|
||||
OperatingSystemFamily::class.java,
|
||||
UsageContext::class.java,
|
||||
UsageContext::class.java
|
||||
).newInstance(
|
||||
name,
|
||||
baseName,
|
||||
group,
|
||||
version,
|
||||
debuggable,
|
||||
optimized,
|
||||
target.getGradleOSFamily(project.objects),
|
||||
linkUsage,
|
||||
runtimeUsage
|
||||
)
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.ProjectProperty
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The plugin allows an IDE to specify some building parameters. These parameters
|
||||
* are passed to the plugin via environment variables. Two variables are supported:
|
||||
* - CONFIGURATION_BUILD_DIR - A path to a destination directory for all compilation tasks.
|
||||
* The IDE should take care about specifying different directories
|
||||
* for different targets. This setting has less priority than
|
||||
* an explicitly specified destination directory in the build script.
|
||||
*
|
||||
* - DEBUGGING_SYMBOLS - If YES, the debug support will be enabled for all artifacts. This option has less
|
||||
* priority than explicitly specified enableDebug option in the build script and
|
||||
* enableDebug project property.
|
||||
*
|
||||
* - KONAN_ENABLE_OPTIMIZATIONS - If YES, optimizations will be enabled for all artifacts by default. This option
|
||||
* has less priority than explicitly specified enableOptimizations option in the
|
||||
* build script.
|
||||
*
|
||||
* Support for environment variables should be explicitly enabled by setting a project property:
|
||||
* konan.useEnvironmentVariables = true.
|
||||
*/
|
||||
|
||||
internal interface EnvironmentVariables {
|
||||
val configurationBuildDir: File?
|
||||
val debuggingSymbols: Boolean
|
||||
val enableOptimizations: Boolean
|
||||
}
|
||||
|
||||
internal class EnvironmentVariablesUnused: EnvironmentVariables {
|
||||
override val configurationBuildDir: File?
|
||||
get() = null
|
||||
|
||||
override val debuggingSymbols: Boolean
|
||||
get() = false
|
||||
|
||||
override val enableOptimizations: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
internal class EnvironmentVariablesImpl(val project: Project): EnvironmentVariables {
|
||||
override val configurationBuildDir: File?
|
||||
get() = System.getenv("CONFIGURATION_BUILD_DIR")?.let {
|
||||
project.file(it)
|
||||
}
|
||||
|
||||
override val debuggingSymbols: Boolean
|
||||
get() = System.getenv("DEBUGGING_SYMBOLS")?.toUpperCase() == "YES"
|
||||
|
||||
override val enableOptimizations: Boolean
|
||||
get() = System.getenv("KONAN_ENABLE_OPTIMIZATIONS")?.toUpperCase() == "YES"
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to https://github.com/gradle/gradle/issues/3468 we cannot use environment
|
||||
* variables in Java 9. Until Gradle API for environment variables is provided
|
||||
* we use project properties instead of them. TODO: Return to using env vars when the issue is fixed.
|
||||
*/
|
||||
internal class EnvironmentVariablesFromProperties(val project: Project): EnvironmentVariables {
|
||||
override val configurationBuildDir: File?
|
||||
get() = project.findProperty(ProjectProperty.KONAN_CONFIGURATION_BUILD_DIR)?.let {
|
||||
project.file(it)
|
||||
}
|
||||
|
||||
override val debuggingSymbols: Boolean
|
||||
get() = project.findProperty(ProjectProperty.KONAN_DEBUGGING_SYMBOLS)?.toString()?.toUpperCase().let {
|
||||
it == "YES" || it == "TRUE"
|
||||
}
|
||||
|
||||
override val enableOptimizations: Boolean
|
||||
get() = project.findProperty(ProjectProperty.KONAN_OPTIMIZATIONS_ENABLE)?.toString()?.toUpperCase().let {
|
||||
it == "YES" || it == "TRUE"
|
||||
}
|
||||
}
|
||||
|
||||
internal val Project.useEnvironmentVariables: Boolean
|
||||
get() = findProperty(ProjectProperty.KONAN_USE_ENVIRONMENT_VARIABLES)?.toString()?.toBoolean() ?: false
|
||||
|
||||
/*
|
||||
TODO: Return to using env vars when the issue is fixed.
|
||||
Take into account the useEnvironmentVariables property (and may be rename it) in the following way:
|
||||
|
||||
if (useEnvironmentVariables) {
|
||||
EnvironmentVariablesImpl(project)
|
||||
} else {
|
||||
EnvironmentVariablesUnused()
|
||||
}
|
||||
*/
|
||||
internal val Project.environmentVariables: EnvironmentVariables
|
||||
get() = EnvironmentVariablesFromProperties(project)
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.NamedDomainObjectFactory
|
||||
import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.internal.reflect.Instantiator
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class KonanArtifactContainer(val project: ProjectInternal)
|
||||
: DefaultPolymorphicDomainObjectContainer<KonanBuildingConfig<*>>(
|
||||
KonanBuildingConfig::class.java,
|
||||
project.services.get(Instantiator::class.java)
|
||||
) {
|
||||
|
||||
private inner class KonanBuildingConfigFactory<T: KonanBuildingConfig<*>>(val configClass: KClass<T>)
|
||||
: NamedDomainObjectFactory<T> {
|
||||
|
||||
var targets: Iterable<String> = emptyList()
|
||||
|
||||
override fun create(name: String?): T =
|
||||
instantiator.newInstance(configClass.java, name, project, targets)
|
||||
}
|
||||
|
||||
private val factories = mutableMapOf<KClass<out KonanBuildingConfig<*>>, KonanBuildingConfigFactory<*>>()
|
||||
|
||||
private fun <T: KonanBuildingConfig<*>> createFactory(configClass: KClass<T>) {
|
||||
val factory = KonanBuildingConfigFactory(configClass)
|
||||
super.registerFactory(configClass.java, factory)
|
||||
factories.put(configClass, factory)
|
||||
}
|
||||
|
||||
init {
|
||||
createFactory(KonanProgram::class)
|
||||
createFactory(KonanDynamic::class)
|
||||
createFactory(KonanFramework::class)
|
||||
createFactory(KonanLibrary::class)
|
||||
createFactory(KonanBitcode::class)
|
||||
createFactory(KonanInteropLibrary::class)
|
||||
}
|
||||
|
||||
private fun determineTargets(configClass: KClass<out KonanBuildingConfig<*>>, args: Map<String, Any?>) {
|
||||
val targetsArg = args["targets"]
|
||||
val targets = when {
|
||||
targetsArg == null -> project.konanExtension.targets
|
||||
targetsArg is Iterable<*> -> targetsArg.map { it.toString() }
|
||||
else -> listOf(targetsArg.toString())
|
||||
}
|
||||
factories[configClass]?.targets = targets
|
||||
}
|
||||
|
||||
private fun <T: KonanBuildingConfig<*>> create(name: String,
|
||||
configClass: KClass<T>,
|
||||
args: Map<String, Any?>,
|
||||
configureAction: Action<T>) {
|
||||
determineTargets(configClass, args)
|
||||
super.create(name, configClass.java, configureAction)
|
||||
}
|
||||
|
||||
private fun <T: KonanBuildingConfig<*>> create(name: String,
|
||||
configClass: KClass<T>,
|
||||
args: Map<String, Any?>,
|
||||
configureAction: T.() -> Unit) {
|
||||
determineTargets(configClass, args)
|
||||
super.create(name, configClass.java, configureAction)
|
||||
}
|
||||
|
||||
private fun <T: KonanBuildingConfig<*>> create(name: String,
|
||||
configClass: KClass<T>,
|
||||
args: Map<String, Any?>) {
|
||||
determineTargets(configClass, args)
|
||||
super.create(name, configClass.java)
|
||||
}
|
||||
|
||||
fun program(args: Map<String, Any?>, name: String) = create(name, KonanProgram::class, args)
|
||||
fun program(args: Map<String, Any?>, name: String, configureAction: Action<KonanProgram>) =
|
||||
create(name, KonanProgram::class, args, configureAction)
|
||||
fun program(args: Map<String, Any?>, name: String, configureAction: KonanProgram.() -> Unit) =
|
||||
create(name, KonanProgram::class, args, configureAction)
|
||||
fun program(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
program(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun dynamic(args: Map<String, Any?>, name: String) = create(name, KonanDynamic::class, args)
|
||||
fun dynamic(args: Map<String, Any?>, name: String, configureAction: Action<KonanDynamic>) =
|
||||
create(name, KonanDynamic::class, args, configureAction)
|
||||
fun dynamic(args: Map<String, Any?>, name: String, configureAction: KonanDynamic.() -> Unit) =
|
||||
create(name, KonanDynamic::class, args, configureAction)
|
||||
fun dynamic(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
dynamic(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun framework(args: Map<String, Any?>, name: String) = create(name, KonanFramework::class, args)
|
||||
fun framework(args: Map<String, Any?>, name: String, configureAction: Action<KonanFramework>) =
|
||||
create(name, KonanFramework::class, args, configureAction)
|
||||
fun framework(args: Map<String, Any?>, name: String, configureAction: KonanFramework.() -> Unit) =
|
||||
create(name, KonanFramework::class, args, configureAction)
|
||||
fun framework(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
framework(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun library(args: Map<String, Any?>, name: String) = create(name, KonanLibrary::class, args)
|
||||
fun library(args: Map<String, Any?>, name: String, configureAction: Action<KonanLibrary>) =
|
||||
create(name, KonanLibrary::class, args, configureAction)
|
||||
fun library(args: Map<String, Any?>, name: String, configureAction: KonanLibrary.() -> Unit) =
|
||||
create(name, KonanLibrary::class, args, configureAction)
|
||||
fun library(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
library(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun bitcode(args: Map<String, Any?>, name: String) = create(name, KonanBitcode::class, args)
|
||||
fun bitcode(args: Map<String, Any?>, name: String, configureAction: Action<KonanBitcode>) =
|
||||
create(name, KonanBitcode::class, args, configureAction)
|
||||
fun bitcode(args: Map<String, Any?>, name: String, configureAction: KonanBitcode.() -> Unit) =
|
||||
create(name, KonanBitcode::class, args, configureAction)
|
||||
fun bitcode(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
bitcode(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun interop(args: Map<String, Any?>, name: String) = create(name, KonanInteropLibrary::class, args)
|
||||
fun interop(args: Map<String, Any?>, name: String, configureAction: Action<KonanInteropLibrary>) =
|
||||
create(name, KonanInteropLibrary::class, args, configureAction)
|
||||
fun interop(args: Map<String, Any?>, name: String, configureAction: KonanInteropLibrary.() -> Unit) =
|
||||
create(name, KonanInteropLibrary::class, args, configureAction)
|
||||
fun interop(args: Map<String, Any?>, name: String, configureAction: Closure<*>) =
|
||||
interop(args, name, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun program(name: String) = program(emptyMap(), name)
|
||||
fun program(name: String, configureAction: Action<KonanProgram>) = program(emptyMap(), name, configureAction)
|
||||
fun program(name: String, configureAction: KonanProgram.() -> Unit) = program(emptyMap(), name, configureAction)
|
||||
fun program(name: String, configureAction: Closure<*>) = program(emptyMap(), name, configureAction)
|
||||
|
||||
fun dynamic(name: String) = dynamic(emptyMap(), name)
|
||||
fun dynamic(name: String, configureAction: Action<KonanDynamic>) = dynamic(emptyMap(), name, configureAction)
|
||||
fun dynamic(name: String, configureAction: KonanDynamic.() -> Unit) = dynamic(emptyMap(), name, configureAction)
|
||||
fun dynamic(name: String, configureAction: Closure<*>) = dynamic(emptyMap(), name, configureAction)
|
||||
|
||||
fun framework(name: String) = framework(emptyMap(), name)
|
||||
fun framework(name: String, configureAction: Action<KonanFramework>) = framework(emptyMap(), name, configureAction)
|
||||
fun framework(name: String, configureAction: KonanFramework.() -> Unit) = framework(emptyMap(), name, configureAction)
|
||||
fun framework(name: String, configureAction: Closure<*>) = framework(emptyMap(), name, configureAction)
|
||||
|
||||
fun library(name: String) = library(emptyMap(), name)
|
||||
fun library(name: String, configureAction: Action<KonanLibrary>) = library(emptyMap(), name, configureAction)
|
||||
fun library(name: String, configureAction: KonanLibrary.() -> Unit) = library(emptyMap(), name, configureAction)
|
||||
fun library(name: String, configureAction: Closure<*>) = library(emptyMap(), name, configureAction)
|
||||
|
||||
fun bitcode(name: String) = bitcode(emptyMap(), name)
|
||||
fun bitcode(name: String, configureAction: Action<KonanBitcode>) = bitcode(emptyMap(), name, configureAction)
|
||||
fun bitcode(name: String, configureAction: KonanBitcode.() -> Unit) = bitcode(emptyMap(), name, configureAction)
|
||||
fun bitcode(name: String, configureAction: Closure<*>) = bitcode(emptyMap(), name, configureAction)
|
||||
|
||||
fun interop(name: String) = interop(emptyMap(), name)
|
||||
fun interop(name: String, configureAction: Action<KonanInteropLibrary>) = interop(emptyMap(), name, configureAction)
|
||||
fun interop(name: String, configureAction: KonanInteropLibrary.() -> Unit) = interop(emptyMap(), name, configureAction)
|
||||
fun interop(name: String, configureAction: Closure<*>) = interop(emptyMap(), name, configureAction)
|
||||
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.*
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.api.plugins.BasePlugin
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
import org.gradle.api.publish.maven.MavenPom
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.gradle.util.WrapUtil
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
|
||||
/** Base class for all Kotlin/Native artifacts. */
|
||||
abstract class KonanBuildingConfig<T: KonanBuildingTask>(private val name_: String,
|
||||
val type: Class<T>,
|
||||
val project: ProjectInternal,
|
||||
val targets: Iterable<String>)
|
||||
: KonanBuildingSpec, Named, DomainObjectSet<T> by WrapUtil.toDomainObjectSet(type) {
|
||||
|
||||
internal val mainVariant = KonanSoftwareComponent(project)
|
||||
override fun getName() = name_
|
||||
|
||||
protected val targetToTask = mutableMapOf<KonanTarget, T>()
|
||||
|
||||
internal val aggregateBuildTask: Task
|
||||
|
||||
internal var pomActions = mutableListOf<Action<MavenPom>>()
|
||||
|
||||
private val konanTargets: Iterable<KonanTarget>
|
||||
get() = project.hostManager.toKonanTargets(targets).distinct()
|
||||
|
||||
init {
|
||||
for (targetName in targets.distinct()) {
|
||||
val konanTarget = project.hostManager.targetByName(targetName)
|
||||
|
||||
if (!project.hostManager.isEnabled(konanTarget)) {
|
||||
project.logger.info("The target is not enabled on the current host: $targetName")
|
||||
continue
|
||||
}
|
||||
if (!targetIsSupported(konanTarget)) {
|
||||
project.logger.info("The target ${targetName} is not supported by the artifact $name")
|
||||
continue
|
||||
}
|
||||
if (this[konanTarget] == null) {
|
||||
val task = createTask(konanTarget)
|
||||
add(task)
|
||||
targetToTask[konanTarget] = task
|
||||
// Allow accessing targets just by their names in Groovy DSL.
|
||||
(this as? ExtensionAware)?.extensions?.add(konanTarget.visibleName, task)
|
||||
}
|
||||
|
||||
if (targetName != konanTarget.visibleName) {
|
||||
createTargetAliasTaskIfDeclared(targetName)
|
||||
}
|
||||
}
|
||||
aggregateBuildTask = createAggregateTask()
|
||||
}
|
||||
|
||||
protected open fun generateTaskName(target: KonanTarget) =
|
||||
"compileKonan${name.capitalize()}${target.visibleName.capitalize()}"
|
||||
|
||||
protected open fun generateAggregateTaskName() =
|
||||
"compileKonan${name.capitalize()}"
|
||||
|
||||
protected open fun generateTargetAliasTaskName(targetName: String) =
|
||||
"compileKonan${name.capitalize()}${targetName.capitalize()}"
|
||||
|
||||
protected abstract fun generateTaskDescription(task: T): String
|
||||
protected abstract fun generateAggregateTaskDescription(task: Task): String
|
||||
protected abstract fun generateTargetAliasTaskDescription(task: Task, targetName: String): String
|
||||
|
||||
protected abstract val defaultBaseDir: File
|
||||
|
||||
protected open fun targetIsSupported(target: KonanTarget): Boolean = true
|
||||
|
||||
data class OutputPlacement(val destinationDir: File, val artifactName: String)
|
||||
|
||||
// There are two options for output placement.
|
||||
// 1. Gradle's build directory. We use it by default, e.g. if user runs Gradle from command line.
|
||||
// In this case all produced files has the same name but are placed in different directories
|
||||
// depending on their targets (e.g. linux/foo.kexe and macbook/foo.kexe).
|
||||
// 2. Custom path provided by IDE. In this case CONFIGURATION_BUILD_DIR environment variable should
|
||||
// contain a path to a destination directory. All produced files are placed in this directory so IDE
|
||||
// should take care about setting different CONFIGURATION_BUILD_DIR for different targets.
|
||||
protected fun determineOutputPlacement(target: KonanTarget): OutputPlacement {
|
||||
val configurationBuildDir = project.environmentVariables.configurationBuildDir
|
||||
return if (configurationBuildDir != null) {
|
||||
OutputPlacement(configurationBuildDir, name)
|
||||
} else {
|
||||
OutputPlacement(defaultBaseDir.targetSubdir(target), name)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun createTask(target: KonanTarget): T =
|
||||
project.tasks.create(generateTaskName(target), type) {
|
||||
val outputDescription = determineOutputPlacement(target)
|
||||
it.init(this, outputDescription.destinationDir, outputDescription.artifactName, target)
|
||||
it.group = BasePlugin.BUILD_GROUP
|
||||
it.description = generateTaskDescription(it)
|
||||
} ?: throw Exception("Cannot create task for target: ${target.visibleName}")
|
||||
|
||||
protected fun createAggregateTask(): Task =
|
||||
project.tasks.create(generateAggregateTaskName()) { task ->
|
||||
task.group = BasePlugin.BUILD_GROUP
|
||||
task.description = generateAggregateTaskDescription(task)
|
||||
this.filter {
|
||||
project.targetIsRequested(it.konanTarget)
|
||||
}.forEach {
|
||||
task.dependsOn(it)
|
||||
}
|
||||
project.compileAllTask.dependsOn(task)
|
||||
}
|
||||
|
||||
protected fun createTargetAliasTaskIfDeclared(targetName: String): Task? {
|
||||
val canonicalTarget = project.hostManager.targetByName(targetName)
|
||||
|
||||
return this[canonicalTarget]?.let { canonicalBuild ->
|
||||
project.tasks.create(generateTargetAliasTaskName(targetName)) {
|
||||
it.group = BasePlugin.BUILD_GROUP
|
||||
it.description = generateTargetAliasTaskDescription(it, targetName)
|
||||
it.dependsOn(canonicalBuild)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal operator fun get(target: KonanTarget) = targetToTask[target]
|
||||
|
||||
fun getByTarget(target: String) = findByTarget(target) ?: throw NoSuchElementException("No such target for artifact $name: ${target}")
|
||||
fun findByTarget(target: String) = this[project.hostManager.targetByName(target)]
|
||||
|
||||
fun getArtifactByTarget(target: String) = getByTarget(target).artifact
|
||||
fun findArtifactByTarget(target: String) = findByTarget(target)?.artifact
|
||||
|
||||
// Common building DSL.
|
||||
|
||||
override fun artifactName(name: String) = forEach { it.artifactName(name) }
|
||||
|
||||
fun baseDir(dir: Any) = forEach { it.destinationDir(project.file(dir).targetSubdir(it.konanTarget)) }
|
||||
|
||||
override fun libraries(closure: Closure<Unit>) = forEach { it.libraries(closure) }
|
||||
override fun libraries(action: Action<KonanLibrariesSpec>) = forEach { it.libraries(action) }
|
||||
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) = forEach { it.libraries(configure) }
|
||||
|
||||
override fun noDefaultLibs(flag: Boolean) = forEach { it.noDefaultLibs(flag) }
|
||||
override fun noEndorsedLibs(flag: Boolean) = forEach { it.noEndorsedLibs(flag) }
|
||||
|
||||
override fun dumpParameters(flag: Boolean) = forEach { it.dumpParameters(flag) }
|
||||
|
||||
override fun extraOpts(vararg values: Any) = forEach { it.extraOpts(*values) }
|
||||
override fun extraOpts(values: List<Any>) = forEach { it.extraOpts(values) }
|
||||
|
||||
fun dependsOn(vararg dependencies: Any?) = forEach { it.dependsOn(*dependencies) }
|
||||
|
||||
fun target(targetString: String, configureAction: T.() -> Unit) {
|
||||
val target = project.hostManager.targetByName(targetString)
|
||||
|
||||
if (!project.hostManager.isEnabled(target)) {
|
||||
project.logger.info("Target '$targetString' of artifact '$name' is not supported on the current host")
|
||||
return
|
||||
}
|
||||
|
||||
val task = this[target] ?:
|
||||
throw InvalidUserDataException("Target '$targetString' is not declared. Please add it into project.konanTasks list")
|
||||
task.configureAction()
|
||||
}
|
||||
fun target(targetString: String, configureAction: Action<T>) =
|
||||
target(targetString) { configureAction.execute(this) }
|
||||
fun target(targetString: String, configureAction: Closure<Unit>) =
|
||||
target(targetString, ConfigureUtil.configureUsing(configureAction))
|
||||
|
||||
fun pom(action: Action<MavenPom>) = pomActions + action
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.*
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget.WASM32
|
||||
import java.io.File
|
||||
|
||||
abstract class KonanCompileConfig<T: KonanCompileTask>(name: String,
|
||||
type: Class<T>,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String>)
|
||||
: KonanBuildingConfig<T>(name, type, project, targets), KonanCompileSpec {
|
||||
|
||||
protected abstract val typeForDescription: String
|
||||
|
||||
override fun generateTaskDescription(task: T) =
|
||||
"Build the Kotlin/Native $typeForDescription '${task.name}' for target '${task.konanTarget}'"
|
||||
|
||||
override fun generateAggregateTaskDescription(task: Task) =
|
||||
"Build the Kotlin/Native $typeForDescription '${task.name}' for all supported and declared targets"
|
||||
|
||||
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
|
||||
"Build the Kotlin/Native $typeForDescription '${task.name}' for target '$targetName'"
|
||||
|
||||
override fun srcDir(dir: Any) = forEach { it.srcDir(dir) }
|
||||
override fun srcFiles(vararg files: Any) = forEach { it.srcFiles(*files) }
|
||||
override fun srcFiles(files: Collection<Any>) = forEach { it.srcFiles(files) }
|
||||
|
||||
override fun nativeLibrary(lib: Any) = forEach { it.nativeLibrary(lib) }
|
||||
override fun nativeLibraries(vararg libs: Any) = forEach { it.nativeLibraries(*libs) }
|
||||
override fun nativeLibraries(libs: FileCollection) = forEach { it.nativeLibraries(libs) }
|
||||
|
||||
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
|
||||
override fun commonSourceSet(sourceSetName: String) = forEach { it.commonSourceSets(sourceSetName) }
|
||||
override fun commonSourceSets(vararg sourceSetNames: String) = forEach { it.commonSourceSets(*sourceSetNames) }
|
||||
override fun enableMultiplatform(flag: Boolean) = forEach { it.enableMultiplatform(flag) }
|
||||
|
||||
override fun linkerOpts(values: List<String>) = forEach { it.linkerOpts(values) }
|
||||
override fun linkerOpts(vararg values: String) = forEach { it.linkerOpts(*values) }
|
||||
|
||||
override fun enableDebug(flag: Boolean) = forEach { it.enableDebug(flag) }
|
||||
override fun noStdLib(flag: Boolean) = forEach { it.noStdLib(flag) }
|
||||
override fun noMain(flag: Boolean) = forEach { it.noMain(flag) }
|
||||
override fun enableOptimizations(flag: Boolean) = forEach { it.enableOptimizations(flag) }
|
||||
override fun enableAssertions(flag: Boolean) = forEach { it.enableAssertions(flag) }
|
||||
|
||||
override fun entryPoint(entryPoint: String) = forEach { it.entryPoint(entryPoint) }
|
||||
|
||||
override fun measureTime(flag: Boolean) = forEach { it.measureTime(flag) }
|
||||
|
||||
override fun dependencies(closure: Closure<Unit>) = forEach { it.dependencies(closure) }
|
||||
}
|
||||
|
||||
open class KonanProgram(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets
|
||||
) : KonanCompileConfig<KonanCompileProgramTask>(name,
|
||||
KonanCompileProgramTask::class.java,
|
||||
project,
|
||||
targets
|
||||
) {
|
||||
override val typeForDescription: String
|
||||
get() = "executable"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanBinBaseDir
|
||||
}
|
||||
|
||||
open class KonanDynamic(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets)
|
||||
: KonanCompileConfig<KonanCompileDynamicTask>(name,
|
||||
KonanCompileDynamicTask::class.java,
|
||||
project,
|
||||
targets
|
||||
) {
|
||||
override val typeForDescription: String
|
||||
get() = "dynamic library"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanBinBaseDir
|
||||
|
||||
override fun targetIsSupported(target: KonanTarget): Boolean = target != WASM32
|
||||
}
|
||||
|
||||
open class KonanFramework(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets)
|
||||
: KonanCompileConfig<KonanCompileFrameworkTask>(name,
|
||||
KonanCompileFrameworkTask::class.java,
|
||||
project,
|
||||
targets
|
||||
) {
|
||||
override val typeForDescription: String
|
||||
get() = "framework"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanBinBaseDir
|
||||
|
||||
override fun targetIsSupported(target: KonanTarget): Boolean =
|
||||
target.family.isAppleFamily
|
||||
}
|
||||
|
||||
open class KonanLibrary(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets)
|
||||
: KonanCompileConfig<KonanCompileLibraryTask>(name,
|
||||
KonanCompileLibraryTask::class.java,
|
||||
project,
|
||||
targets
|
||||
) {
|
||||
override val typeForDescription: String
|
||||
get() = "library"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanLibsBaseDir
|
||||
}
|
||||
|
||||
open class KonanBitcode(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets)
|
||||
: KonanCompileConfig<KonanCompileBitcodeTask>(name,
|
||||
KonanCompileBitcodeTask::class.java,
|
||||
project,
|
||||
targets
|
||||
) {
|
||||
override val typeForDescription: String
|
||||
get() = "bitcode"
|
||||
|
||||
override fun generateTaskDescription(task: KonanCompileBitcodeTask) =
|
||||
"Generates bitcode for the artifact '${task.name}' and target '${task.konanTarget}'"
|
||||
|
||||
override fun generateAggregateTaskDescription(task: Task) =
|
||||
"Generates bitcode for the artifact '${task.name}' for all supported and declared targets'"
|
||||
|
||||
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
|
||||
"Generates bitcode for the artifact '${task.name}' for '$targetName'"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanBitcodeBaseDir
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanInteropSpec.IncludeDirectoriesSpec
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanInteropTask
|
||||
import java.io.File
|
||||
|
||||
open class KonanInteropLibrary(name: String,
|
||||
project: ProjectInternal,
|
||||
targets: Iterable<String> = project.konanExtension.targets
|
||||
) : KonanBuildingConfig<KonanInteropTask>(name, KonanInteropTask::class.java, project, targets),
|
||||
KonanInteropSpec
|
||||
{
|
||||
|
||||
override fun generateTaskDescription(task: KonanInteropTask) =
|
||||
"Build the Kotlin/Native interop library '${task.name}' for target '${task.konanTarget}'"
|
||||
|
||||
override fun generateAggregateTaskDescription(task: Task) =
|
||||
"Build the Kotlin/Native interop library '${task.name}' for all supported and declared targets'"
|
||||
|
||||
override fun generateTargetAliasTaskDescription(task: Task, targetName: String) =
|
||||
"Build the Kotlin/Native interop library '${task.name}' for '$targetName'"
|
||||
|
||||
override val defaultBaseDir: File
|
||||
get() = project.konanLibsBaseDir
|
||||
|
||||
// DSL
|
||||
|
||||
inner class IncludeDirectoriesSpecImpl: IncludeDirectoriesSpec {
|
||||
override fun allHeaders(vararg includeDirs: Any) = allHeaders(includeDirs.toList())
|
||||
override fun allHeaders(includeDirs: Collection<Any>) = forEach {
|
||||
it.includeDirs.allHeaders(includeDirs)
|
||||
}
|
||||
|
||||
override fun headerFilterOnly(vararg includeDirs: Any) = headerFilterOnly(includeDirs.toList())
|
||||
override fun headerFilterOnly(includeDirs: Collection<Any>) = forEach {
|
||||
it.includeDirs.headerFilterOnly(includeDirs)
|
||||
}
|
||||
}
|
||||
|
||||
val includeDirs = IncludeDirectoriesSpecImpl()
|
||||
|
||||
override fun defFile(file: Any) = forEach { it.defFile(file) }
|
||||
|
||||
override fun packageName(value: String) = forEach { it.packageName(value) }
|
||||
|
||||
override fun compilerOpts(vararg values: String) = forEach { it.compilerOpts(*values) }
|
||||
|
||||
override fun headers(vararg files: Any) = forEach { it.headers(*files) }
|
||||
|
||||
override fun headers(files: FileCollection) = forEach { it.headers(files) }
|
||||
|
||||
override fun includeDirs(vararg values: Any) = forEach { it.includeDirs(*values) }
|
||||
override fun includeDirs(closure: Closure<Unit>) = includeDirs(ConfigureUtil.configureUsing(closure))
|
||||
override fun includeDirs(action: Action<IncludeDirectoriesSpec>) = includeDirs { action.execute(this) }
|
||||
override fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit) = includeDirs.configure()
|
||||
|
||||
override fun linkerOpts(values: List<String>) = forEach { it.linkerOpts(values) }
|
||||
override fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
|
||||
|
||||
override fun link(vararg files: Any) = forEach { it.link(*files) }
|
||||
override fun link(files: FileCollection) = forEach { it.link(files) }
|
||||
override fun dependencies(closure: Closure<Unit>) = forEach { it.dependencies(closure) }
|
||||
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import org.gradle.api.InvalidUserDataException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanArtifactWithLibrariesTask
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanBuildingTask
|
||||
import org.jetbrains.kotlin.konan.*
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.jetbrains.kotlin.konan.library.defaultResolver
|
||||
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryImpl
|
||||
import org.jetbrains.kotlin.konan.target.Distribution
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.library.SearchPathResolver
|
||||
import java.io.File
|
||||
|
||||
open class KonanLibrariesSpec(
|
||||
@Internal val task: KonanArtifactWithLibrariesTask,
|
||||
@Internal val project: Project
|
||||
) {
|
||||
|
||||
@InputFiles val files = mutableSetOf<FileCollection>()
|
||||
|
||||
@Input val namedKlibs = mutableSetOf<String>()
|
||||
|
||||
@Internal val artifacts = mutableListOf<KonanBuildingTask>()
|
||||
|
||||
val artifactFiles: List<File>
|
||||
@InputFiles get() = artifacts.map { it.artifact }
|
||||
|
||||
@Internal val explicitRepos = mutableSetOf<File>()
|
||||
|
||||
val repos: Set<File>
|
||||
@Input get() = mutableSetOf<File>().apply {
|
||||
addAll(explicitRepos)
|
||||
add(task.destinationDir) // TODO: Check if task is a library - create a Library interface
|
||||
add(task.destinationDir) // TODO: Check if task is a library - create a Library interface
|
||||
add(task.project.konanLibsBaseDir.targetSubdir(target))
|
||||
addAll(artifacts.flatMap { it.libraries.repos })
|
||||
addAll(task.platformConfiguration.files.map { it.parentFile })
|
||||
}
|
||||
|
||||
val target: KonanTarget
|
||||
@Internal get() = task.konanTarget
|
||||
|
||||
private val friendsTasks = mutableSetOf<KonanBuildingTask>()
|
||||
|
||||
@get:Internal // Taken into account by tasks's dependOn.
|
||||
val friends: Set<File> get() = mutableSetOf<File>().apply {
|
||||
addAll(friendsTasks.map { it.artifact })
|
||||
}
|
||||
|
||||
// DSL Methods
|
||||
|
||||
/** Absolute path */
|
||||
fun file(file: Any) = files.add(project.files(file))
|
||||
fun files(vararg files: Any) = this.files.addAll(files.map { project.files(it) })
|
||||
fun files(collection: FileCollection) = this.files.add(collection)
|
||||
|
||||
/** The compiler with search the library in repos */
|
||||
fun klib(lib: String) = namedKlibs.add(lib)
|
||||
fun klibs(vararg libs: String) = namedKlibs.addAll(libs)
|
||||
fun klibs(libs: Iterable<String>) = namedKlibs.addAll(libs)
|
||||
|
||||
private fun klibInternal(lib: KonanBuildingConfig<*>, friend: Boolean) {
|
||||
if (!(lib is KonanLibrary || lib is KonanInteropLibrary)) {
|
||||
throw InvalidUserDataException("Config ${lib.name} is not a library")
|
||||
}
|
||||
|
||||
val libraryTask = lib[target] ?:
|
||||
throw InvalidUserDataException("Library ${lib.name} has no target ${target.visibleName}")
|
||||
|
||||
if (libraryTask == task) {
|
||||
throw InvalidUserDataException("Attempt to use a library as its own dependency: " +
|
||||
"${task.name} (in project: ${project.path})")
|
||||
}
|
||||
artifacts.add(libraryTask)
|
||||
task.dependsOn(libraryTask)
|
||||
if (friend) friendsTasks.add(libraryTask)
|
||||
}
|
||||
|
||||
/** Direct link to a config */
|
||||
fun klib(lib: KonanLibrary) = klibInternal(lib, false)
|
||||
/** Direct link to a config */
|
||||
fun klib(lib: KonanInteropLibrary) = klibInternal(lib, false)
|
||||
|
||||
/** Artifact in the specified project by name */
|
||||
fun artifact(libraryProject: Project, name: String, friend: Boolean) {
|
||||
project.evaluationDependsOn(libraryProject)
|
||||
klibInternal(libraryProject.konanArtifactsContainer.getByName(name), friend)
|
||||
}
|
||||
|
||||
fun artifact(libraryProject: Project, name: String) = artifact(libraryProject, name, false)
|
||||
/** Artifact in the current project by name */
|
||||
fun artifact(name: String, friend: Boolean) = artifact(project, name, friend)
|
||||
|
||||
fun artifact(name: String) = artifact(project, name, false)
|
||||
|
||||
/** Artifact by direct link */
|
||||
fun artifact(artifact: KonanLibrary) = klib(artifact)
|
||||
/** Direct link to a config */
|
||||
fun artifact(artifact: KonanInteropLibrary) = klib(artifact)
|
||||
|
||||
private fun allArtifactsFromInternal(libraryProjects: Array<out Project>,
|
||||
filter: (KonanBuildingConfig<*>) -> Boolean) {
|
||||
libraryProjects.forEach { prj ->
|
||||
project.evaluationDependsOn(prj)
|
||||
prj.konanArtifactsContainer.filter(filter).forEach {
|
||||
klibInternal(it, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** All libraries (both interop and non-interop ones) from the projects by direct references */
|
||||
fun allLibrariesFrom(vararg libraryProjects: Project) = allArtifactsFromInternal(libraryProjects) {
|
||||
it is KonanLibrary || it is KonanInteropLibrary
|
||||
}
|
||||
|
||||
/** All interop libraries from the projects by direct references */
|
||||
fun allInteropLibrariesFrom(vararg libraryProjects: Project) = allArtifactsFromInternal(libraryProjects) {
|
||||
it is KonanInteropLibrary
|
||||
}
|
||||
|
||||
/** Add repo for library search */
|
||||
fun useRepo(directory: Any) = explicitRepos.add(project.file(directory))
|
||||
/** Add repos for library search */
|
||||
fun useRepos(vararg directories: Any) = directories.forEach { useRepo(it) }
|
||||
/** Add repos for library search */
|
||||
fun useRepos(directories: Iterable<Any>) = directories.forEach { useRepo(it) }
|
||||
|
||||
private fun Project.evaluationDependsOn(another: Project) {
|
||||
if (this != another) { evaluationDependsOn(another.path) }
|
||||
}
|
||||
|
||||
fun asFiles(): List<File> = asFiles(
|
||||
defaultResolver(
|
||||
repos.map { it.absolutePath },
|
||||
task.konanTarget,
|
||||
Distribution(project.konanHome)
|
||||
)
|
||||
)
|
||||
|
||||
fun asFiles(resolver: SearchPathResolver<*>): List<File> = mutableListOf<File>().apply {
|
||||
files.flatMapTo(this) { it.files }
|
||||
addAll(artifactFiles)
|
||||
addAll(task.platformConfiguration.files)
|
||||
namedKlibs.mapTo(this) { project.file(resolver.resolve(it).libraryFile.absolutePath) }
|
||||
}
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import org.gradle.api.*
|
||||
import org.gradle.api.component.ComponentWithVariants
|
||||
import org.gradle.api.component.SoftwareComponent
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.internal.FeaturePreviews
|
||||
import org.gradle.api.internal.component.SoftwareComponentInternal
|
||||
import org.gradle.api.internal.component.UsageContext
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.api.plugins.BasePlugin
|
||||
import org.gradle.api.publish.PublishingExtension
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal
|
||||
import org.gradle.language.cpp.internal.NativeVariantIdentity
|
||||
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.Companion.COMPILE_ALL_TASK_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanToolingModelBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.*
|
||||
import org.jetbrains.kotlin.konan.CURRENT
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.jetbrains.kotlin.konan.parseCompilerVersion
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.target.buildDistribution
|
||||
import org.jetbrains.kotlin.konan.target.customerDistribution
|
||||
import org.jetbrains.kotlin.konan.util.DependencyProcessor
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* We use the following properties:
|
||||
* org.jetbrains.kotlin.native.home - directory where compiler is located (aka dist in konan project output).
|
||||
* org.jetbrains.kotlin.native.version - a konan compiler version for downloading.
|
||||
* konan.build.targets - list of targets to build (by default all the declared targets are built).
|
||||
* konan.jvmArgs - additional args to be passed to a JVM executing the compiler/cinterop tool.
|
||||
*/
|
||||
|
||||
internal fun Project.warnAboutDeprecatedProperty(property: KonanPlugin.ProjectProperty) =
|
||||
property.deprecatedPropertyName?.let { deprecated ->
|
||||
if (project.hasProperty(deprecated)) {
|
||||
logger.warn("Project property '$deprecated' is deprecated. Use '${property.propertyName}' instead.")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Project.hasProperty(property: KonanPlugin.ProjectProperty) = with(property) {
|
||||
when {
|
||||
hasProperty(propertyName) -> true
|
||||
deprecatedPropertyName != null && hasProperty(deprecatedPropertyName) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Project.findProperty(property: KonanPlugin.ProjectProperty): Any? = with(property) {
|
||||
return findProperty(propertyName) ?: deprecatedPropertyName?.let { findProperty(it) }
|
||||
}
|
||||
|
||||
internal fun Project.getProperty(property: KonanPlugin.ProjectProperty) = findProperty(property)
|
||||
?: throw IllegalArgumentException("No such property in the project: ${property.propertyName}")
|
||||
|
||||
internal fun Project.getProperty(property: KonanPlugin.ProjectProperty, defaultValue: Any) =
|
||||
findProperty(property) ?: defaultValue
|
||||
|
||||
internal fun Project.setProperty(property: KonanPlugin.ProjectProperty, value: Any) {
|
||||
extensions.extraProperties.set(property.propertyName, value)
|
||||
}
|
||||
|
||||
// konanHome extension is set by downloadKonanCompiler task.
|
||||
internal val Project.konanHome: String
|
||||
get() {
|
||||
assert(hasProperty(KonanPlugin.ProjectProperty.KONAN_HOME))
|
||||
return project.file(getProperty(KonanPlugin.ProjectProperty.KONAN_HOME)).canonicalPath
|
||||
}
|
||||
|
||||
internal val Project.konanVersion: CompilerVersion
|
||||
get() = project.findProperty(KonanPlugin.ProjectProperty.KONAN_VERSION)
|
||||
?.toString()?.let { CompilerVersion.fromString(it) }
|
||||
?: CompilerVersion.CURRENT
|
||||
|
||||
internal val Project.konanBuildRoot get() = buildDir.resolve("konan")
|
||||
internal val Project.konanBinBaseDir get() = konanBuildRoot.resolve("bin")
|
||||
internal val Project.konanLibsBaseDir get() = konanBuildRoot.resolve("libs")
|
||||
internal val Project.konanBitcodeBaseDir get() = konanBuildRoot.resolve("bitcode")
|
||||
|
||||
internal fun File.targetSubdir(target: KonanTarget) = resolve(target.visibleName)
|
||||
|
||||
internal val Project.konanDefaultSrcFiles get() = fileTree("${projectDir.canonicalPath}/src/main/kotlin")
|
||||
internal fun Project.konanDefaultDefFile(libName: String)
|
||||
= file("${projectDir.canonicalPath}/src/main/c_interop/$libName.def")
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val Project.konanArtifactsContainer: KonanArtifactContainer
|
||||
get() = extensions.getByName(KonanPlugin.ARTIFACTS_CONTAINER_NAME) as KonanArtifactContainer
|
||||
|
||||
// TODO: The Kotlin/Native compiler is downloaded manually by a special task so the compilation tasks
|
||||
// are configured without the compile distribution. After target management refactoring
|
||||
// we need .properties files from the distribution to configure targets. This is worked around here
|
||||
// by using HostManager instead of PlatformManager. But we need to download the compiler at the configuration
|
||||
// stage (e.g. by getting it from maven as a plugin dependency) and bring back the PlatformManager here.
|
||||
internal val Project.hostManager: HostManager
|
||||
get() = findProperty("hostManager") as HostManager? ?:
|
||||
if (hasProperty("org.jetbrains.kotlin.native.experimentalTargets"))
|
||||
HostManager(buildDistribution(rootProject.rootDir.absolutePath), true)
|
||||
else
|
||||
HostManager(customerDistribution(konanHome))
|
||||
|
||||
internal val Project.konanTargets: List<KonanTarget>
|
||||
get() = hostManager.toKonanTargets(konanExtension.targets)
|
||||
.filter{ hostManager.isEnabled(it) }
|
||||
.distinct()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal val Project.konanExtension: KonanExtension
|
||||
get() = extensions.getByName(KonanPlugin.KONAN_EXTENSION_NAME) as KonanExtension
|
||||
|
||||
internal val Project.konanCompilerDownloadTask
|
||||
get() = tasks.getByName(KonanPlugin.KONAN_DOWNLOAD_TASK_NAME)
|
||||
|
||||
internal val Project.requestedTargets
|
||||
get() = findProperty(KonanPlugin.ProjectProperty.KONAN_BUILD_TARGETS)?.let {
|
||||
it.toString().trim().split("\\s+".toRegex())
|
||||
}.orEmpty()
|
||||
|
||||
internal val Project.jvmArgs
|
||||
get() = (findProperty(KonanPlugin.ProjectProperty.KONAN_JVM_ARGS) as String?)?.split("\\s+".toRegex()).orEmpty()
|
||||
|
||||
internal val Project.compileAllTask
|
||||
get() = getOrCreateTask(COMPILE_ALL_TASK_NAME)
|
||||
|
||||
internal fun Project.targetIsRequested(target: KonanTarget): Boolean {
|
||||
val targets = requestedTargets
|
||||
return (targets.isEmpty() || targets.contains(target.visibleName) || targets.contains("all"))
|
||||
}
|
||||
|
||||
/** Looks for task with given name in the given project. Throws [UnknownTaskException] if there's not such task. */
|
||||
private fun Project.getTask(name: String): Task = tasks.getByPath(name)
|
||||
|
||||
/**
|
||||
* Looks for task with given name in the given project.
|
||||
* If such task isn't found, will create it. Returns created/found task.
|
||||
*/
|
||||
private fun Project.getOrCreateTask(name: String): Task = with(tasks) {
|
||||
findByPath(name) ?: create(name, DefaultTask::class.java)
|
||||
}
|
||||
|
||||
internal fun Project.konanCompilerName(): String =
|
||||
"kotlin-native-${project.simpleOsName}-${project.konanVersion}"
|
||||
|
||||
internal fun Project.konanCompilerDownloadDir(): String =
|
||||
DependencyProcessor.localKonanDir.resolve(project.konanCompilerName()).absolutePath
|
||||
|
||||
// region Useful extensions and functions ---------------------------------------
|
||||
|
||||
internal fun MutableList<String>.addArg(parameter: String, value: String) {
|
||||
add(parameter)
|
||||
add(value)
|
||||
}
|
||||
|
||||
internal fun MutableList<String>.addArgs(parameter: String, values: Iterable<String>) {
|
||||
values.forEach {
|
||||
addArg(parameter, it)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MutableList<String>.addArgIfNotNull(parameter: String, value: String?) {
|
||||
if (value != null) {
|
||||
addArg(parameter, value)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MutableList<String>.addKey(key: String, enabled: Boolean) {
|
||||
if (enabled) {
|
||||
add(key)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MutableList<String>.addFileArgs(parameter: String, values: FileCollection) {
|
||||
values.files.forEach {
|
||||
addArg(parameter, it.canonicalPath)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MutableList<String>.addFileArgs(parameter: String, values: Collection<FileCollection>) {
|
||||
values.forEach {
|
||||
addFileArgs(parameter, it)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
internal fun dumpProperties(task: Task) {
|
||||
fun Iterable<String>.dump() = joinToString(prefix = "[", separator = ",\n${" ".repeat(22)}", postfix = "]")
|
||||
fun Collection<FileCollection>.dump() = flatMap { it.files }.map { it.canonicalPath }.dump()
|
||||
when (task) {
|
||||
is KonanCompileTask -> with(task) {
|
||||
println()
|
||||
println("Compilation task: $name")
|
||||
println("destinationDir : $destinationDir")
|
||||
println("artifact : ${artifact.canonicalPath}")
|
||||
println("srcFiles : ${srcFiles.dump()}")
|
||||
println("produce : $produce")
|
||||
println("libraries : ${libraries.files.dump()}")
|
||||
println(" : ${libraries.artifacts.map {
|
||||
it.artifact.canonicalPath
|
||||
}.dump()}")
|
||||
println(" : ${libraries.namedKlibs.dump()}")
|
||||
println("nativeLibraries : ${nativeLibraries.dump()}")
|
||||
println("linkerOpts : $linkerOpts")
|
||||
println("enableDebug : $enableDebug")
|
||||
println("noStdLib : $noStdLib")
|
||||
println("noMain : $noMain")
|
||||
println("enableOptimization : $enableOptimizations")
|
||||
println("enableAssertions : $enableAssertions")
|
||||
println("noDefaultLibs : $noDefaultLibs")
|
||||
println("noEndorsedLibs : $noEndorsedLibs")
|
||||
println("target : $target")
|
||||
println("languageVersion : $languageVersion")
|
||||
println("apiVersion : $apiVersion")
|
||||
println("konanVersion : ${CompilerVersion.CURRENT}")
|
||||
println("konanHome : $konanHome")
|
||||
println()
|
||||
}
|
||||
is KonanInteropTask -> with(task) {
|
||||
println()
|
||||
println("Stub generation task: $name")
|
||||
println("destinationDir : $destinationDir")
|
||||
println("artifact : $artifact")
|
||||
println("libraries : ${libraries.files.dump()}")
|
||||
println(" : ${libraries.artifacts.map {
|
||||
it.artifact.canonicalPath
|
||||
}.dump()}")
|
||||
println(" : ${libraries.namedKlibs.dump()}")
|
||||
println("defFile : $defFile")
|
||||
println("target : $target")
|
||||
println("packageName : $packageName")
|
||||
println("compilerOpts : $compilerOpts")
|
||||
println("linkerOpts : $linkerOpts")
|
||||
println("headers : ${headers.dump()}")
|
||||
println("linkFiles : ${linkFiles.dump()}")
|
||||
println("konanVersion : ${CompilerVersion.CURRENT}")
|
||||
println("konanHome : $konanHome")
|
||||
println()
|
||||
}
|
||||
else -> {
|
||||
println("Unsupported task.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class KonanExtension {
|
||||
var targets = mutableListOf("host")
|
||||
var languageVersion: String? = null
|
||||
var apiVersion: String? = null
|
||||
var jvmArgs = mutableListOf<String>()
|
||||
}
|
||||
|
||||
open class KonanSoftwareComponent(val project: ProjectInternal?): SoftwareComponentInternal, ComponentWithVariants {
|
||||
private val usages = mutableSetOf<UsageContext>()
|
||||
override fun getUsages(): MutableSet<out UsageContext> = usages
|
||||
|
||||
private val variants = mutableSetOf<SoftwareComponent>()
|
||||
override fun getName() = "main"
|
||||
|
||||
override fun getVariants(): Set<SoftwareComponent> = variants
|
||||
|
||||
fun addVariant(component: SoftwareComponent) = variants.add(component)
|
||||
}
|
||||
|
||||
class KonanPlugin @Inject constructor(private val registry: ToolingModelBuilderRegistry)
|
||||
: Plugin<ProjectInternal> {
|
||||
|
||||
enum class ProjectProperty(val propertyName: String, val deprecatedPropertyName: String? = null) {
|
||||
KONAN_HOME ("org.jetbrains.kotlin.native.home", "konan.home"),
|
||||
KONAN_VERSION ("org.jetbrains.kotlin.native.version"),
|
||||
KONAN_BUILD_TARGETS ("konan.build.targets"),
|
||||
KONAN_JVM_ARGS ("konan.jvmArgs"),
|
||||
KONAN_USE_ENVIRONMENT_VARIABLES("konan.useEnvironmentVariables"),
|
||||
DOWNLOAD_COMPILER ("download.compiler"),
|
||||
|
||||
// Properties used instead of env vars until https://github.com/gradle/gradle/issues/3468 is fixed.
|
||||
// TODO: Remove them when an API for env vars is provided.
|
||||
KONAN_CONFIGURATION_BUILD_DIR ("konan.configuration.build.dir"),
|
||||
KONAN_DEBUGGING_SYMBOLS ("konan.debugging.symbols"),
|
||||
KONAN_OPTIMIZATIONS_ENABLE ("konan.optimizations.enable"),
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val ARTIFACTS_CONTAINER_NAME = "konanArtifacts"
|
||||
internal const val KONAN_DOWNLOAD_TASK_NAME = "checkKonanCompiler"
|
||||
internal const val KONAN_GENERATE_CMAKE_TASK_NAME = "generateCMake"
|
||||
internal const val COMPILE_ALL_TASK_NAME = "compileKonan"
|
||||
|
||||
internal const val KONAN_EXTENSION_NAME = "konan"
|
||||
|
||||
internal val REQUIRED_GRADLE_VERSION = GradleVersion.version("4.7")
|
||||
}
|
||||
|
||||
private fun Project.cleanKonan() = project.tasks.withType(KonanBuildingTask::class.java).forEach {
|
||||
project.delete(it.artifact)
|
||||
}
|
||||
|
||||
private fun checkGradleVersion() = GradleVersion.current().let { current ->
|
||||
check(current >= REQUIRED_GRADLE_VERSION) {
|
||||
"Kotlin/Native Gradle plugin is incompatible with this version of Gradle.\n" +
|
||||
"The minimal required version is $REQUIRED_GRADLE_VERSION\n" +
|
||||
"Current version is ${current}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun apply(project: ProjectInternal?) {
|
||||
if (project == null) {
|
||||
return
|
||||
}
|
||||
checkGradleVersion()
|
||||
registry.register(KonanToolingModelBuilder)
|
||||
project.plugins.apply("base")
|
||||
// Create necessary tasks and extensions.
|
||||
project.tasks.create(KONAN_DOWNLOAD_TASK_NAME, KonanCompilerDownloadTask::class.java)
|
||||
project.tasks.create(KONAN_GENERATE_CMAKE_TASK_NAME, KonanGenerateCMakeTask::class.java)
|
||||
project.extensions.create(KONAN_EXTENSION_NAME, KonanExtension::class.java)
|
||||
val container = project.extensions.create(
|
||||
KonanArtifactContainer::class.java,
|
||||
ARTIFACTS_CONTAINER_NAME,
|
||||
KonanArtifactContainer::class.java,
|
||||
project
|
||||
)
|
||||
|
||||
project.warnAboutDeprecatedProperty(ProjectProperty.KONAN_HOME)
|
||||
|
||||
// Set additional project properties like org.jetbrains.kotlin.native.home, konan.build.targets etc.
|
||||
if (!project.hasProperty(ProjectProperty.KONAN_HOME)) {
|
||||
project.setProperty(ProjectProperty.KONAN_HOME, project.konanCompilerDownloadDir())
|
||||
project.setProperty(ProjectProperty.DOWNLOAD_COMPILER, true)
|
||||
}
|
||||
|
||||
// Create and set up aggregate building tasks.
|
||||
val compileKonanTask = project.getOrCreateTask(COMPILE_ALL_TASK_NAME).apply {
|
||||
group = BasePlugin.BUILD_GROUP
|
||||
description = "Compiles all the Kotlin/Native artifacts"
|
||||
}
|
||||
project.getTask("build").apply {
|
||||
dependsOn(compileKonanTask)
|
||||
}
|
||||
project.getTask("clean").apply {
|
||||
doLast { project.cleanKonan() }
|
||||
}
|
||||
|
||||
val runTask = project.getOrCreateTask("run")
|
||||
project.afterEvaluate {
|
||||
project.konanArtifactsContainer
|
||||
.filterIsInstance(KonanProgram::class.java)
|
||||
.forEach { program ->
|
||||
program.forEach { compile ->
|
||||
compile.runTask?.let { runTask.dependsOn(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable multiplatform support
|
||||
project.pluginManager.apply(KotlinNativePlatformPlugin::class.java)
|
||||
project.afterEvaluate {
|
||||
project.pluginManager.withPlugin("maven-publish") {
|
||||
container.all { buildingConfig ->
|
||||
val konanSoftwareComponent = buildingConfig.mainVariant
|
||||
project.extensions.configure(PublishingExtension::class.java) {
|
||||
val builtArtifact = buildingConfig.name
|
||||
val mavenPublication = it.publications.maybeCreate(builtArtifact, MavenPublication::class.java)
|
||||
mavenPublication.apply {
|
||||
artifactId = builtArtifact
|
||||
groupId = project.group.toString()
|
||||
from(konanSoftwareComponent)
|
||||
}
|
||||
(mavenPublication as MavenPublicationInternal).publishWithOriginalFileName()
|
||||
buildingConfig.pomActions.forEach {
|
||||
mavenPublication.pom(it)
|
||||
}
|
||||
}
|
||||
|
||||
project.extensions.configure(PublishingExtension::class.java) {
|
||||
val publishing = it
|
||||
for (v in konanSoftwareComponent.variants) {
|
||||
publishing.publications.create(v.name, MavenPublication::class.java) { mavenPublication ->
|
||||
val coordinates = (v as NativeVariantIdentity).coordinates
|
||||
project.logger.info("variant with coordinates($coordinates) and module: ${coordinates.module}")
|
||||
mavenPublication.artifactId = coordinates.module.name
|
||||
mavenPublication.groupId = coordinates.group
|
||||
mavenPublication.version = coordinates.version
|
||||
mavenPublication.from(v)
|
||||
(mavenPublication as MavenPublicationInternal).publishWithOriginalFileName()
|
||||
buildingConfig.pomActions.forEach {
|
||||
mavenPublication.pom(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.file.FileCollection
|
||||
|
||||
interface KonanArtifactSpec {
|
||||
fun artifactName(name: String)
|
||||
}
|
||||
|
||||
interface KonanArtifactWithLibrariesSpec: KonanArtifactSpec {
|
||||
fun libraries(closure: Closure<Unit>)
|
||||
fun libraries(action: Action<KonanLibrariesSpec>)
|
||||
fun libraries(configure: KonanLibrariesSpec.() -> Unit)
|
||||
|
||||
fun noDefaultLibs(flag: Boolean)
|
||||
fun noEndorsedLibs(flag: Boolean)
|
||||
fun dependencies(closure: Closure<Unit>)
|
||||
}
|
||||
|
||||
interface KonanBuildingSpec: KonanArtifactWithLibrariesSpec {
|
||||
fun dumpParameters(flag: Boolean)
|
||||
|
||||
fun extraOpts(vararg values: Any)
|
||||
fun extraOpts(values: List<Any>)
|
||||
}
|
||||
|
||||
interface KonanCompileSpec: KonanBuildingSpec {
|
||||
fun srcDir(dir: Any)
|
||||
|
||||
fun srcFiles(vararg files: Any)
|
||||
fun srcFiles(files: Collection<Any>)
|
||||
|
||||
// DSL. Native libraries.
|
||||
|
||||
fun nativeLibrary(lib: Any)
|
||||
fun nativeLibraries(vararg libs: Any)
|
||||
fun nativeLibraries(libs: FileCollection)
|
||||
|
||||
// DSL. Multiplatform projects
|
||||
fun enableMultiplatform(flag: Boolean)
|
||||
|
||||
// TODO: Get rid of commonSourceSet in 0.7
|
||||
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
|
||||
fun commonSourceSet(sourceSetName: String)
|
||||
fun commonSourceSets(vararg sourceSetNames: String)
|
||||
|
||||
// DSL. Other parameters.
|
||||
|
||||
fun linkerOpts(vararg values: String)
|
||||
fun linkerOpts(values: List<String>)
|
||||
|
||||
fun enableDebug(flag: Boolean)
|
||||
fun noStdLib(flag: Boolean)
|
||||
fun noMain(flag: Boolean)
|
||||
fun enableOptimizations(flag: Boolean)
|
||||
fun enableAssertions(flag: Boolean)
|
||||
|
||||
fun entryPoint(entryPoint: String)
|
||||
|
||||
fun measureTime(flag: Boolean)
|
||||
}
|
||||
|
||||
interface KonanInteropSpec: KonanBuildingSpec {
|
||||
|
||||
interface IncludeDirectoriesSpec {
|
||||
fun allHeaders(vararg includeDirs: Any)
|
||||
fun allHeaders(includeDirs: Collection<Any>)
|
||||
|
||||
fun headerFilterOnly(vararg includeDirs: Any)
|
||||
fun headerFilterOnly(includeDirs: Collection<Any>)
|
||||
}
|
||||
|
||||
fun defFile(file: Any)
|
||||
|
||||
fun packageName(value: String)
|
||||
|
||||
fun compilerOpts(vararg values: String)
|
||||
|
||||
fun header(file: Any) = headers(file)
|
||||
fun headers(vararg files: Any)
|
||||
fun headers(files: FileCollection)
|
||||
|
||||
fun includeDirs(vararg values: Any)
|
||||
|
||||
fun includeDirs(closure: Closure<Unit>)
|
||||
fun includeDirs(action: Action<IncludeDirectoriesSpec>)
|
||||
fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit)
|
||||
|
||||
fun linkerOpts(vararg values: String)
|
||||
fun linkerOpts(values: List<String>)
|
||||
|
||||
fun link(vararg files: Any)
|
||||
fun link(files: FileCollection)
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import org.gradle.api.Named
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin.ProjectProperty.KONAN_HOME
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.konan.util.DependencyProcessor
|
||||
|
||||
internal interface KonanToolRunner: Named {
|
||||
val mainClass: String
|
||||
val classpath: FileCollection
|
||||
val jvmArgs: List<String>
|
||||
val environment: Map<String, Any>
|
||||
|
||||
fun run(args: List<String>)
|
||||
fun run(vararg args: String) = run(args.toList())
|
||||
}
|
||||
|
||||
internal abstract class KonanCliRunner(
|
||||
val toolName: String,
|
||||
val fullName: String,
|
||||
val project: Project,
|
||||
private val additionalJvmArgs: List<String>,
|
||||
private val konanHome: String
|
||||
): KonanToolRunner {
|
||||
override val mainClass = "org.jetbrains.kotlin.cli.utilities.MainKt"
|
||||
|
||||
override fun getName() = toolName
|
||||
|
||||
// We need to unset some environment variables which are set by XCode and may potentially affect the tool executed.
|
||||
protected val blacklistEnvironment: List<String> by lazy {
|
||||
KonanPlugin::class.java.getResourceAsStream("/env_blacklist")?.let { stream ->
|
||||
stream.reader().use { it.readLines() }
|
||||
} ?: emptyList<String>()
|
||||
}
|
||||
|
||||
protected val blacklistProperties: Set<String> =
|
||||
setOf("java.endorsed.dirs")
|
||||
|
||||
override val classpath: FileCollection =
|
||||
project.fileTree("$konanHome/konan/lib/")
|
||||
.apply { include("*.jar") }
|
||||
|
||||
override val jvmArgs = HostManager.defaultJvmArgs.toMutableList().apply {
|
||||
if (additionalJvmArgs.none { it.startsWith("-Xmx") } &&
|
||||
project.jvmArgs.none { it.startsWith("-Xmx") }) {
|
||||
add("-Xmx3G")
|
||||
}
|
||||
addAll(additionalJvmArgs)
|
||||
addAll(project.jvmArgs)
|
||||
}
|
||||
|
||||
override val environment = mutableMapOf("LIBCLANG_DISABLE_CRASH_RECOVERY" to "1")
|
||||
|
||||
private fun String.escapeQuotes() = replace("\"", "\\\"")
|
||||
|
||||
private fun Sequence<Pair<String, String>>.escapeQuotesForWindows() =
|
||||
if (HostManager.hostIsMingw) {
|
||||
map { (key, value) -> key.escapeQuotes() to value.escapeQuotes() }
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
open protected fun transformArgs(args: List<String>): List<String> = args
|
||||
|
||||
override fun run(args: List<String>) {
|
||||
project.logger.info("Run tool: $toolName with args: ${args.joinToString(separator = " ")}")
|
||||
if (classpath.isEmpty) {
|
||||
throw IllegalStateException("Classpath of the tool is empty: $toolName\n" +
|
||||
"Probably the '${KONAN_HOME.propertyName}' project property contains an incorrect path.\n" +
|
||||
"Please change it to the compiler root directory and rerun the build.")
|
||||
}
|
||||
|
||||
project.javaexec { spec ->
|
||||
spec.main = mainClass
|
||||
spec.classpath = classpath
|
||||
spec.jvmArgs(jvmArgs)
|
||||
spec.systemProperties(
|
||||
System.getProperties().asSequence()
|
||||
.map { (k, v) -> k.toString() to v.toString() }
|
||||
.filter { (k, _) -> k !in blacklistProperties }
|
||||
.escapeQuotesForWindows()
|
||||
.toMap()
|
||||
)
|
||||
spec.args(listOf(toolName) + transformArgs(args))
|
||||
blacklistEnvironment.forEach { spec.environment.remove(it) }
|
||||
spec.environment(environment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class KonanInteropRunner(
|
||||
project: Project,
|
||||
additionalJvmArgs: List<String> = emptyList(),
|
||||
konanHome: String = project.konanHome
|
||||
) : KonanCliRunner("cinterop", "Kotlin/Native cinterop tool", project, additionalJvmArgs, konanHome) {
|
||||
init {
|
||||
if (HostManager.host == KonanTarget.MINGW_X64) {
|
||||
//TODO: Oh-ho-ho fix it in more convinient way.
|
||||
environment.put("PATH", DependencyProcessor.defaultDependenciesRoot.absolutePath +
|
||||
"\\msys2-mingw-w64-x86_64-clang-llvm-lld-compiler_rt-8.0.1" +
|
||||
"\\bin;${environment.get("PATH")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class KonanCompilerRunner(
|
||||
project: Project,
|
||||
additionalJvmArgs: List<String> = emptyList(),
|
||||
val useArgFile: Boolean = true,
|
||||
konanHome: String = project.konanHome
|
||||
) : KonanCliRunner("konanc", "Kotlin/Native compiler", project, additionalJvmArgs, konanHome)
|
||||
{
|
||||
override fun transformArgs(args: List<String>): List<String> {
|
||||
if (!useArgFile) {
|
||||
return args
|
||||
}
|
||||
|
||||
val argFile = createTempFile(prefix = "konancArgs", suffix = ".lst").apply {
|
||||
deleteOnExit()
|
||||
}
|
||||
argFile.printWriter().use { writer ->
|
||||
args.forEach {
|
||||
writer.println(it)
|
||||
}
|
||||
}
|
||||
|
||||
return listOf("@${argFile.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
internal class KonanKlibRunner(
|
||||
project: Project,
|
||||
additionalJvmArgs: List<String> = emptyList(),
|
||||
konanHome: String = project.konanHome
|
||||
) : KonanCliRunner("klib", "Klib management tool", project, additionalJvmArgs, konanHome)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.jetbrains.kotlin.gradle.plugin.konan
|
||||
|
||||
import org.gradle.api.Named
|
||||
import org.gradle.api.NamedDomainObjectContainer
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformImplementationPluginBase
|
||||
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileTask
|
||||
import javax.inject.Inject
|
||||
|
||||
open class KotlinNativePlatformPlugin: KotlinPlatformImplementationPluginBase("native") {
|
||||
|
||||
private val Project.konanMultiplatformTasks: Collection<KonanCompileTask>
|
||||
get() = tasks.withType(KonanCompileTask::class.java).filter { it.enableMultiplatform }
|
||||
|
||||
override fun configurationsForCommonModuleDependency(project: Project) = emptyList<Configuration>()
|
||||
|
||||
open class RequestedCommonSourceSet @Inject constructor(private val name: String): Named {
|
||||
override fun getName() = name
|
||||
}
|
||||
|
||||
override fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: Named, platformProject: Project) {
|
||||
val commonSourceSetName = commonSourceSet.name
|
||||
|
||||
platformProject.konanMultiplatformTasks
|
||||
.filter { it.commonSourceSets.contains(commonSourceSetName) }
|
||||
.forEach { task: KonanCompileTask ->
|
||||
getKotlinSourceDirectorySetSafe(commonSourceSet)!!.srcDirs.forEach {
|
||||
task.commonSrcDir(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> =
|
||||
project.container(RequestedCommonSourceSet::class.java).apply {
|
||||
project.konanMultiplatformTasks.forEach { task ->
|
||||
task.commonSourceSets.forEach { maybeCreate(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.*
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.gradle.api.attributes.AttributeContainer
|
||||
import org.gradle.api.attributes.Usage
|
||||
import org.gradle.api.capabilities.Capability
|
||||
import org.gradle.api.internal.component.UsageContext
|
||||
import org.gradle.api.internal.tasks.DefaultTaskDependency
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.language.cpp.CppBinary
|
||||
import org.gradle.language.cpp.internal.DefaultUsageContext
|
||||
import org.gradle.nativeplatform.Linkage
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.jetbrains.kotlin.gradle.plugin.experimental.internal.compatibleVariantIdentity
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.*
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
internal val Project.host
|
||||
get() = HostManager.host.visibleName
|
||||
|
||||
internal val Project.simpleOsName
|
||||
get() = HostManager.simpleOsName()
|
||||
|
||||
/** A task with a KonanTarget specified. */
|
||||
abstract class KonanTargetableTask: DefaultTask() {
|
||||
|
||||
@get:Input
|
||||
val konanTargetName: String
|
||||
get() = konanTarget.name
|
||||
|
||||
@get:Internal
|
||||
internal lateinit var konanTarget: KonanTarget
|
||||
|
||||
internal open fun init(target: KonanTarget) {
|
||||
this.konanTarget = target
|
||||
}
|
||||
|
||||
val isCrossCompile: Boolean
|
||||
@Internal get() = (konanTarget != HostManager.host)
|
||||
|
||||
val target: String
|
||||
@Internal get() = konanTarget.visibleName
|
||||
}
|
||||
|
||||
/** A task building an artifact. */
|
||||
abstract class KonanArtifactTask: KonanTargetableTask(), KonanArtifactSpec {
|
||||
|
||||
open val artifact: File
|
||||
@OutputFile get() = destinationDir.resolve(artifactFullName)
|
||||
|
||||
@Internal lateinit var destinationDir: File
|
||||
@Internal lateinit var artifactName: String
|
||||
@Internal lateinit var platformConfiguration: Configuration
|
||||
@Internal lateinit var configuration: Configuration
|
||||
|
||||
protected val artifactFullName: String
|
||||
@Internal get() = "$artifactPrefix$artifactName$artifactSuffix"
|
||||
|
||||
val artifactPath: String
|
||||
@Internal get() = artifact.canonicalPath
|
||||
|
||||
protected abstract val artifactSuffix: String
|
||||
@Internal get
|
||||
|
||||
protected abstract val artifactPrefix: String
|
||||
@Internal get
|
||||
|
||||
internal open fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
|
||||
super.init(target)
|
||||
this.destinationDir = destinationDir
|
||||
this.artifactName = artifactName
|
||||
configuration = project.configurations.maybeCreate("artifact$artifactName")
|
||||
platformConfiguration = project.configurations.create("artifact${artifactName}_${target.name}")
|
||||
platformConfiguration.extendsFrom(configuration)
|
||||
platformConfiguration.attributes{
|
||||
it.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, Usage.NATIVE_LINK))
|
||||
it.attribute(CppBinary.LINKAGE_ATTRIBUTE, Linkage.STATIC)
|
||||
it.attribute(CppBinary.OPTIMIZED_ATTRIBUTE, false)
|
||||
it.attribute(CppBinary.DEBUGGABLE_ATTRIBUTE, false)
|
||||
it.attribute(Attribute.of("org.gradle.native.kotlin.platform", String::class.java), target.name)
|
||||
}
|
||||
|
||||
val artifactNameWithoutSuffix = artifact.name.removeSuffix("$artifactSuffix")
|
||||
project.pluginManager.withPlugin("maven-publish") {
|
||||
platformConfiguration.artifacts.add(object: PublishArtifact {
|
||||
override fun getName(): String = artifactNameWithoutSuffix
|
||||
override fun getExtension() = if (artifactSuffix.startsWith('.')) artifactSuffix.substring(1) else artifactSuffix
|
||||
override fun getType() = artifactSuffix
|
||||
override fun getClassifier():String? = target.name
|
||||
override fun getFile() = artifact
|
||||
override fun getDate() = Date(artifact.lastModified())
|
||||
override fun getBuildDependencies(): TaskDependency =
|
||||
DefaultTaskDependency().apply { add(this@KonanArtifactTask) }
|
||||
})
|
||||
val objectFactory = project.objects
|
||||
val linkUsage = objectFactory.named(Usage::class.java, Usage.NATIVE_LINK)
|
||||
val konanSoftwareComponent = config.mainVariant
|
||||
val variantName = "${artifactNameWithoutSuffix}_${target.name}"
|
||||
val context = DefaultUsageContext(object:UsageContext {
|
||||
override fun getUsage(): Usage = linkUsage
|
||||
override fun getName(): String = "${variantName}Link"
|
||||
override fun getCapabilities(): MutableSet<out Capability> = mutableSetOf()
|
||||
override fun getDependencies(): MutableSet<out ModuleDependency> = mutableSetOf()
|
||||
override fun getDependencyConstraints(): MutableSet<out DependencyConstraint> = mutableSetOf()
|
||||
override fun getArtifacts(): MutableSet<out PublishArtifact> = platformConfiguration.allArtifacts
|
||||
override fun getAttributes(): AttributeContainer = platformConfiguration.attributes
|
||||
override fun getGlobalExcludes(): Set<ExcludeRule> = emptySet()
|
||||
}, platformConfiguration.allArtifacts, platformConfiguration)
|
||||
konanSoftwareComponent.addVariant(
|
||||
compatibleVariantIdentity(
|
||||
project,
|
||||
variantName,
|
||||
project.provider{ artifactName },
|
||||
project.provider{ project.group.toString() },
|
||||
project.provider{ project.version.toString() },
|
||||
false,
|
||||
false,
|
||||
target,
|
||||
context,
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun dependencies(closure: Closure<Unit>) {
|
||||
if (konanTarget in project.konanTargets)
|
||||
project.dependencies(closure)
|
||||
}
|
||||
// DSL.
|
||||
|
||||
override fun artifactName(name: String) {
|
||||
artifactName = name
|
||||
}
|
||||
|
||||
fun destinationDir(dir: Any) {
|
||||
destinationDir = project.file(dir)
|
||||
}
|
||||
}
|
||||
|
||||
/** Task building an artifact with libraries */
|
||||
abstract class KonanArtifactWithLibrariesTask: KonanArtifactTask(), KonanArtifactWithLibrariesSpec {
|
||||
@Nested
|
||||
val libraries = KonanLibrariesSpec(this, project)
|
||||
|
||||
@Input
|
||||
var noDefaultLibs = false
|
||||
|
||||
@Input
|
||||
var noEndorsedLibs = false
|
||||
|
||||
// DSL
|
||||
|
||||
override fun libraries(closure: Closure<Unit>) = libraries(ConfigureUtil.configureUsing(closure))
|
||||
override fun libraries(action: Action<KonanLibrariesSpec>) = libraries { action.execute(this) }
|
||||
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) { libraries.configure() }
|
||||
|
||||
override fun noDefaultLibs(flag: Boolean) {
|
||||
noDefaultLibs = flag
|
||||
}
|
||||
|
||||
override fun noEndorsedLibs(flag: Boolean) {
|
||||
noEndorsedLibs = flag
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import org.gradle.api.tasks.Console
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanModelArtifact
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
|
||||
/** Base class for both interop and compiler tasks. */
|
||||
abstract class KonanBuildingTask: KonanArtifactWithLibrariesTask(), KonanBuildingSpec {
|
||||
|
||||
@get:Internal
|
||||
internal abstract val toolRunner: KonanToolRunner
|
||||
internal abstract fun toModelArtifact(): KonanModelArtifact
|
||||
|
||||
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
|
||||
dependsOn(project.konanCompilerDownloadTask)
|
||||
super.init(config, destinationDir, artifactName, target)
|
||||
}
|
||||
|
||||
@Console
|
||||
var dumpParameters: Boolean = false
|
||||
|
||||
@Input
|
||||
val extraOpts = mutableListOf<String>()
|
||||
|
||||
val konanHome
|
||||
@Input get() = project.konanHome
|
||||
|
||||
val konanVersion
|
||||
@Input get() = project.konanVersion.toString(true, true)
|
||||
|
||||
@TaskAction
|
||||
abstract fun run()
|
||||
|
||||
// DSL.
|
||||
|
||||
override fun dumpParameters(flag: Boolean) {
|
||||
dumpParameters = flag
|
||||
}
|
||||
|
||||
override fun extraOpts(vararg values: Any) = extraOpts(values.toList())
|
||||
override fun extraOpts(values: List<Any>) {
|
||||
extraOpts.addAll(values.map { it.toString() })
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package org.jetbrains.kotlin.gradle.plugin.konan.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanCompilerRunner
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.hostManager
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanHome
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
|
||||
enum class KonanCacheKind(val outputKind: CompilerOutputKind) {
|
||||
STATIC(CompilerOutputKind.STATIC_CACHE),
|
||||
DYNAMIC(CompilerOutputKind.DYNAMIC_CACHE)
|
||||
}
|
||||
|
||||
open class KonanCacheTask: DefaultTask() {
|
||||
@InputDirectory
|
||||
lateinit var originalKlib: File
|
||||
|
||||
// Taken into account by the [cacheFile] property.
|
||||
@Internal
|
||||
lateinit var cacheRoot: File
|
||||
|
||||
@get:Input
|
||||
lateinit var target: String
|
||||
|
||||
@get:Internal
|
||||
// TODO: Reuse NativeCacheKind from Big Kotlin plugin when it is available.
|
||||
val cacheDirectory: File
|
||||
get() = cacheRoot.resolve("$target-g$cacheKind")
|
||||
|
||||
@get:OutputDirectory
|
||||
protected val cacheFile: File
|
||||
get() {
|
||||
val klibName = originalKlib.let {
|
||||
if (it.isDirectory) it.name else it.nameWithoutExtension
|
||||
}
|
||||
return cacheDirectory.resolve("${klibName}-cache")
|
||||
}
|
||||
|
||||
@Input
|
||||
var cacheKind: KonanCacheKind = KonanCacheKind.STATIC
|
||||
|
||||
@Input
|
||||
/** Path to a compiler distribution that is used to build this cache. */
|
||||
val compilerDistributionPath: Property<File> = project.objects.property(File::class.java).apply {
|
||||
set(project.provider { project.file(project.konanHome) })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun compile() {
|
||||
// Compiler doesn't create a cache if the cacheFile already exists. So we need to remove it manually.
|
||||
if (cacheFile.exists()) {
|
||||
val deleted = cacheFile.deleteRecursively()
|
||||
check(deleted) { "Cannot delete stale cache: ${cacheFile.absolutePath}" }
|
||||
}
|
||||
|
||||
val args = listOf(
|
||||
"-g",
|
||||
"-target", target,
|
||||
"-produce", cacheKind.outputKind.name.toLowerCase(),
|
||||
"-Xadd-cache=${originalKlib.absolutePath}",
|
||||
"-Xcache-directory=${cacheDirectory.absolutePath}"
|
||||
)
|
||||
KonanCompilerRunner(project, konanHome = compilerDistributionPath.get().absolutePath).run(args)
|
||||
}
|
||||
}
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.codehaus.groovy.runtime.GStringImpl
|
||||
import org.gradle.api.file.ConfigurableFileTree
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.process.CommandLineArgumentProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanModelArtifact
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanModelArtifactImpl
|
||||
import org.jetbrains.kotlin.konan.library.defaultResolver
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.Distribution
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* A task compiling the target executable/library using Kotlin/Native compiler
|
||||
*/
|
||||
abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec {
|
||||
|
||||
@get:Internal
|
||||
override val toolRunner = KonanCompilerRunner(project, project.konanExtension.jvmArgs)
|
||||
|
||||
abstract val produce: CompilerOutputKind
|
||||
@Internal get
|
||||
|
||||
// Output artifact --------------------------------------------------------
|
||||
|
||||
override val artifactSuffix: String
|
||||
@Internal get() = produce.suffix(konanTarget)
|
||||
|
||||
override val artifactPrefix: String
|
||||
@Internal get() = produce.prefix(konanTarget)
|
||||
|
||||
// Multiplatform support --------------------------------------------------
|
||||
|
||||
@Input var commonSourceSets = listOf("main")
|
||||
|
||||
@Internal var enableMultiplatform = false
|
||||
|
||||
private val commonSrcFiles_ = mutableSetOf<FileCollection>()
|
||||
val commonSrcFiles: Collection<FileCollection>
|
||||
@Internal get() = if (enableMultiplatform) commonSrcFiles_ else emptyList()
|
||||
|
||||
// Other compilation parameters -------------------------------------------
|
||||
|
||||
private val srcFiles_ = mutableSetOf<FileCollection>()
|
||||
val srcFiles: Collection<FileCollection>
|
||||
@Internal get() = srcFiles_.takeIf { !it.isEmpty() } ?: listOf(project.konanDefaultSrcFiles)
|
||||
|
||||
val allSources: Collection<FileCollection>
|
||||
@InputFiles get() = listOf(srcFiles, commonSrcFiles).flatten()
|
||||
|
||||
private val allSourceFiles: List<File>
|
||||
get() = allSources
|
||||
.flatMap { it.files }
|
||||
.filter { it.name.endsWith(".kt") }
|
||||
|
||||
@InputFiles val nativeLibraries = mutableSetOf<FileCollection>()
|
||||
|
||||
@Input val linkerOpts = mutableListOf<String>()
|
||||
|
||||
@Input var enableDebug = project.findProperty("enableDebug")?.toString()?.toBoolean()
|
||||
?: project.environmentVariables.debuggingSymbols
|
||||
|
||||
@Input var noStdLib = false
|
||||
@Input var noMain = false
|
||||
@Input var enableOptimizations = project.environmentVariables.enableOptimizations
|
||||
@Input var enableAssertions = false
|
||||
|
||||
@Optional @Input var entryPoint: String? = null
|
||||
|
||||
@Console var measureTime = false
|
||||
|
||||
val languageVersion : String?
|
||||
@Optional @Input get() = project.konanExtension.languageVersion
|
||||
val apiVersion : String?
|
||||
@Optional @Input get() = project.konanExtension.apiVersion
|
||||
|
||||
/**
|
||||
* Is the two-stage compilation enabled.
|
||||
*
|
||||
* In regular (one-stage) compilation, sources are directly compiled into a final native binary.
|
||||
* In two-stage compilation, sources are compiled into a klib first and then a final native binary is produced from this klib.
|
||||
*/
|
||||
@get:Input
|
||||
abstract val enableTwoStageCompilation: Boolean
|
||||
|
||||
protected fun directoryToKt(dir: Any) = project.fileTree(dir).apply {
|
||||
include("**/*.kt")
|
||||
exclude { it.file.startsWith(project.buildDir) }
|
||||
}
|
||||
|
||||
// Command line ------------------------------------------------------------
|
||||
|
||||
// Exclude elements matching the predicate.
|
||||
private fun List<String>.excludeFlags(predicate: (String) -> Boolean) = filterNot(predicate)
|
||||
|
||||
// Exclude the listed elements.
|
||||
private fun List<String>.excludeFlags(vararg keys: String) = keys.toSet().let { keysToExclude ->
|
||||
excludeFlags { it in keysToExclude }
|
||||
}
|
||||
|
||||
// Exclude the arguments passed by the given keys.
|
||||
// E.g. if the list contains the following elements: ["-l", "foo", "-r", "bar"],
|
||||
// call exclude("-r") returns the following list: ["-l", "foo"].
|
||||
private fun List<String>.excludeArguments(vararg args: String): List<String> {
|
||||
val argsToExclude = args.toSet()
|
||||
val xPrefixesToExclude = argsToExclude.filter { it.startsWith("-X") }.map { "$it=" }
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
var i = 0
|
||||
while (i < size) {
|
||||
val key = this[i]
|
||||
when {
|
||||
key in argsToExclude -> {
|
||||
// Skip the key and the following arg.
|
||||
i++
|
||||
}
|
||||
// Support args passed as -X<arg>=<value>.
|
||||
xPrefixesToExclude.any { key.startsWith(it) } -> { /* Skip the key. */ }
|
||||
else -> result += key
|
||||
}
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Don't include coverage flags into the first stage because they are not supported when compiling a klib.
|
||||
private fun firstStageExtraOpts() = extraOpts
|
||||
.excludeFlags("-Xcoverage")
|
||||
.excludeArguments("-Xcoverage-file", "-Xlibrary-to-cover")
|
||||
|
||||
// Don't include the -Xemit-lazy-objc-header flag into
|
||||
// the second stage because this stage have no sources.
|
||||
private fun secondStageExtraOpts() = extraOpts
|
||||
.excludeArguments("-Xemit-lazy-objc-header")
|
||||
|
||||
/** Args passed to the compiler at the first stage of two-stage compilation (klib building). */
|
||||
protected fun buildFirstStageArgs(klibPath: String) = mutableListOf<String>().apply {
|
||||
addArg("-output", klibPath)
|
||||
addArg("-produce", CompilerOutputKind.LIBRARY.name.toLowerCase())
|
||||
|
||||
addAll(buildCommonArgs())
|
||||
|
||||
addAll(firstStageExtraOpts())
|
||||
|
||||
allSourceFiles.mapTo(this) { it.absolutePath }
|
||||
commonSrcFiles
|
||||
.flatMap { it.files }
|
||||
.mapTo(this) { "-Xcommon-sources=${it.absolutePath}" }
|
||||
}
|
||||
|
||||
/** Args passed to the compiler at the second stage of two-stage compilation (producing a final binary from the klib). */
|
||||
protected fun buildSecondStageArgs(klibPath: String) = mutableListOf<String>().apply {
|
||||
addArg("-output", artifact.canonicalPath)
|
||||
addArg("-produce", produce.name.toLowerCase())
|
||||
addArgIfNotNull("-entry", entryPoint)
|
||||
|
||||
addAll(buildCommonArgs())
|
||||
|
||||
addFileArgs("-native-library", nativeLibraries)
|
||||
linkerOpts.forEach {
|
||||
addArg("-linker-option", it)
|
||||
}
|
||||
|
||||
addAll(secondStageExtraOpts())
|
||||
|
||||
add("-Xinclude=${klibPath}")
|
||||
}
|
||||
|
||||
/** Args passed to the compiler at both stages of the two-stage compilation and during the singe-stage compilation. */
|
||||
protected fun buildCommonArgs() = mutableListOf<String>().apply {
|
||||
addArgs("-repo", libraries.repos.map { it.canonicalPath })
|
||||
|
||||
if (platformConfiguration.files.isNotEmpty()) {
|
||||
platformConfiguration.files.filter { it.name.endsWith(".klib") }.forEach {
|
||||
// The library's directory is added in libraries.repos.
|
||||
addArg("-library", it.nameWithoutExtension)
|
||||
}
|
||||
}
|
||||
addFileArgs("-library", libraries.files)
|
||||
addArgs("-library", libraries.namedKlibs)
|
||||
// The library's directory is added in libraries.repos.
|
||||
addArgs("-library", libraries.artifacts.map { it.artifact.nameWithoutExtension })
|
||||
|
||||
addArgIfNotNull("-target", konanTarget.visibleName)
|
||||
addArgIfNotNull("-language-version", languageVersion)
|
||||
addArgIfNotNull("-api-version", apiVersion)
|
||||
addArgIfNotNull("-entry", entryPoint)
|
||||
|
||||
addKey("-g", enableDebug)
|
||||
addKey("-nostdlib", noStdLib)
|
||||
addKey("-nomain", noMain)
|
||||
addKey("-opt", enableOptimizations)
|
||||
addKey("-ea", enableAssertions)
|
||||
addKey("-Xtime", measureTime)
|
||||
addKey("-Xprofile-phases", measureTime)
|
||||
addKey("-no-default-libs", noDefaultLibs)
|
||||
addKey("-no-endorsed-libs", noEndorsedLibs)
|
||||
addKey("-Xmulti-platform", enableMultiplatform)
|
||||
|
||||
if (libraries.friends.isNotEmpty())
|
||||
addArg("-friend-modules", libraries.friends.joinToString(File.pathSeparator))
|
||||
}
|
||||
|
||||
/** Args passed to the compiler if the two-stage compilation is disabled. */
|
||||
fun buildSingleStageArgs() = mutableListOf<String>().apply {
|
||||
addArg("-output", artifact.canonicalPath)
|
||||
addArg("-produce", produce.name.toLowerCase())
|
||||
addArgIfNotNull("-entry", entryPoint)
|
||||
|
||||
addAll(buildCommonArgs())
|
||||
|
||||
addFileArgs("-native-library", nativeLibraries)
|
||||
|
||||
linkerOpts.forEach {
|
||||
addArg("-linker-option", it)
|
||||
}
|
||||
|
||||
addAll(extraOpts)
|
||||
|
||||
allSourceFiles.mapTo(this) { it.absolutePath }
|
||||
commonSrcFiles
|
||||
.flatMap { it.files }
|
||||
.mapTo(this) { "-Xcommon-sources=${it.absolutePath}" }
|
||||
}
|
||||
|
||||
// region DSL.
|
||||
|
||||
// DSL. Input/output files.
|
||||
|
||||
override fun srcDir(dir: Any) {
|
||||
srcFiles_.add(directoryToKt(dir))
|
||||
}
|
||||
override fun srcFiles(vararg files: Any) {
|
||||
srcFiles_.add(project.files(files))
|
||||
}
|
||||
override fun srcFiles(files: Collection<Any>) = srcFiles(*files.toTypedArray())
|
||||
|
||||
// DSL. Native libraries.
|
||||
|
||||
override fun nativeLibrary(lib: Any) = nativeLibraries(lib)
|
||||
override fun nativeLibraries(vararg libs: Any) {
|
||||
nativeLibraries.add(project.files(*libs))
|
||||
}
|
||||
override fun nativeLibraries(libs: FileCollection) {
|
||||
nativeLibraries.add(libs)
|
||||
}
|
||||
|
||||
// DSL. Multiplatform projects.
|
||||
|
||||
override fun enableMultiplatform(flag: Boolean) {
|
||||
enableMultiplatform = flag
|
||||
}
|
||||
|
||||
@Deprecated("Use commonSourceSets instead", ReplaceWith("commonSourceSets(sourceSetName)"))
|
||||
override fun commonSourceSet(sourceSetName: String) {
|
||||
commonSourceSets = listOf(sourceSetName)
|
||||
enableMultiplatform(true)
|
||||
}
|
||||
|
||||
override fun commonSourceSets(vararg sourceSetNames: String) {
|
||||
commonSourceSets = sourceSetNames.toList()
|
||||
enableMultiplatform(true)
|
||||
}
|
||||
|
||||
internal fun commonSrcDir(dir: Any) {
|
||||
commonSrcFiles_.add(directoryToKt(dir))
|
||||
}
|
||||
|
||||
// DSL. Other parameters.
|
||||
|
||||
override fun linkerOpts(values: List<String>) = linkerOpts(*values.toTypedArray())
|
||||
override fun linkerOpts(vararg values: String) {
|
||||
linkerOpts.addAll(values)
|
||||
}
|
||||
|
||||
override fun enableDebug(flag: Boolean) {
|
||||
enableDebug = flag
|
||||
}
|
||||
|
||||
override fun noStdLib(flag: Boolean) {
|
||||
noStdLib = flag
|
||||
}
|
||||
|
||||
override fun noMain(flag: Boolean) {
|
||||
noMain = flag
|
||||
}
|
||||
|
||||
override fun enableOptimizations(flag: Boolean) {
|
||||
enableOptimizations = flag
|
||||
}
|
||||
|
||||
override fun enableAssertions(flag: Boolean) {
|
||||
enableAssertions = flag
|
||||
}
|
||||
|
||||
override fun entryPoint(entryPoint: String) {
|
||||
this.entryPoint = entryPoint
|
||||
}
|
||||
|
||||
override fun measureTime(flag: Boolean) {
|
||||
measureTime = flag
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region IDE model
|
||||
override fun toModelArtifact(): KonanModelArtifact {
|
||||
val repos = libraries.repos
|
||||
val resolver = defaultResolver(
|
||||
repos.map { it.absolutePath },
|
||||
konanTarget,
|
||||
Distribution(project.konanHome)
|
||||
)
|
||||
|
||||
return KonanModelArtifactImpl(
|
||||
artifactName,
|
||||
artifact,
|
||||
produce,
|
||||
konanTarget.name,
|
||||
name,
|
||||
allSources.filterIsInstance(ConfigurableFileTree::class.java).map { it.dir },
|
||||
allSourceFiles,
|
||||
libraries.asFiles(resolver),
|
||||
repos.toList()
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
||||
override fun run() {
|
||||
destinationDir.mkdirs()
|
||||
if (dumpParameters) {
|
||||
dumpProperties(this)
|
||||
}
|
||||
if (enableTwoStageCompilation) {
|
||||
logger.info("Start two-stage compilation")
|
||||
val intermediateDir = project.konanBuildRoot
|
||||
.resolve("intermediate")
|
||||
.targetSubdir(konanTarget)
|
||||
.apply { mkdirs() }
|
||||
val klibPrefix = CompilerOutputKind.LIBRARY.prefix(konanTarget)
|
||||
val klibSuffix = CompilerOutputKind.LIBRARY.suffix(konanTarget)
|
||||
val intermediateKlib = intermediateDir.resolve("$klibPrefix$artifactName$klibSuffix").absolutePath
|
||||
logger.info("Start first stage")
|
||||
toolRunner.run(buildFirstStageArgs(intermediateKlib))
|
||||
logger.info("Start second stage")
|
||||
toolRunner.run(buildSecondStageArgs(intermediateKlib))
|
||||
} else {
|
||||
toolRunner.run(buildSingleStageArgs())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class KonanCompileNativeBinary: KonanCompileTask() {
|
||||
@Input
|
||||
override var enableTwoStageCompilation: Boolean = false
|
||||
}
|
||||
|
||||
open class KonanCompileProgramTask: KonanCompileNativeBinary() {
|
||||
override val produce: CompilerOutputKind get() = CompilerOutputKind.PROGRAM
|
||||
|
||||
@Internal
|
||||
var runTask: Exec? = null
|
||||
|
||||
inner class RunArgumentProvider(): CommandLineArgumentProvider {
|
||||
override fun asArguments() = project.findProperty("runArgs")?.let {
|
||||
it.toString().split(' ')
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
// Create tasks to run supported executables.
|
||||
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
|
||||
super.init(config, destinationDir, artifactName, target)
|
||||
if (!isCrossCompile && !project.hasProperty("konanNoRun")) {
|
||||
runTask = project.tasks.create("run${artifactName.capitalize()}", Exec::class.java).apply {
|
||||
group = "run"
|
||||
dependsOn(this@KonanCompileProgramTask)
|
||||
val artifactPathClosure = object : Closure<String>(this) {
|
||||
override fun call() = artifactPath
|
||||
}
|
||||
// Use GString to evaluate a path to the artifact lazily thus allow changing it at configuration phase.
|
||||
val lazyArtifactPath = GStringImpl(arrayOf(artifactPathClosure), arrayOf(""))
|
||||
executable(lazyArtifactPath)
|
||||
// Add values passed in the runArgs project property as arguments.
|
||||
argumentProviders.add(RunArgumentProvider())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class KonanCompileDynamicTask: KonanCompileNativeBinary() {
|
||||
override val produce: CompilerOutputKind get() = CompilerOutputKind.DYNAMIC
|
||||
|
||||
val headerFile: File
|
||||
@OutputFile get() = destinationDir.resolve("$artifactPrefix${artifactName}_api.h")
|
||||
}
|
||||
|
||||
open class KonanCompileFrameworkTask: KonanCompileNativeBinary() {
|
||||
override val produce: CompilerOutputKind get() = CompilerOutputKind.FRAMEWORK
|
||||
|
||||
override val artifact
|
||||
@OutputDirectory get() = super.artifact
|
||||
}
|
||||
|
||||
open class KonanCompileLibraryTask: KonanCompileTask() {
|
||||
override val produce: CompilerOutputKind get() = CompilerOutputKind.LIBRARY
|
||||
override val enableTwoStageCompilation: Boolean = false
|
||||
}
|
||||
|
||||
open class KonanCompileBitcodeTask: KonanCompileNativeBinary() {
|
||||
override val produce: CompilerOutputKind get() = CompilerOutputKind.BITCODE
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleScriptException
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.*
|
||||
import org.jetbrains.kotlin.konan.MetaVersion
|
||||
import org.jetbrains.kotlin.konan.util.DependencyProcessor
|
||||
import org.jetbrains.kotlin.konan.util.DependencySource
|
||||
import java.io.IOException
|
||||
|
||||
open class KonanCompilerDownloadTask : DefaultTask() {
|
||||
|
||||
internal companion object {
|
||||
internal const val BASE_DOWNLOAD_URL = "https://download.jetbrains.com/kotlin/native/builds"
|
||||
}
|
||||
|
||||
/**
|
||||
* If true the task will also download dependencies for targets specified by the konan.targets project extension.
|
||||
*/
|
||||
@Internal var downloadDependencies: Boolean = false
|
||||
|
||||
@TaskAction
|
||||
fun downloadAndExtract() {
|
||||
if (!project.hasProperty(KonanPlugin.ProjectProperty.DOWNLOAD_COMPILER)) {
|
||||
val konanHome = project.getProperty(KonanPlugin.ProjectProperty.KONAN_HOME)
|
||||
logger.info("Use a user-defined compiler path: $konanHome")
|
||||
} else {
|
||||
try {
|
||||
val downloadUrlDirectory = buildString {
|
||||
append("$BASE_DOWNLOAD_URL/")
|
||||
val version = project.konanVersion
|
||||
when (version.meta) {
|
||||
MetaVersion.DEV -> append("dev/")
|
||||
else -> append("releases/")
|
||||
}
|
||||
append("$version/")
|
||||
append(project.simpleOsName)
|
||||
}
|
||||
val konanCompiler = project.konanCompilerName()
|
||||
val parentDir = DependencyProcessor.localKonanDir
|
||||
logger.info("Downloading Kotlin/Native compiler from $downloadUrlDirectory/$konanCompiler into $parentDir")
|
||||
DependencyProcessor(
|
||||
parentDir,
|
||||
downloadUrlDirectory,
|
||||
mapOf(konanCompiler to listOf(DependencySource.Remote.Public))
|
||||
).run()
|
||||
} catch (e: IOException) {
|
||||
throw GradleScriptException("Cannot download Kotlin/Native compiler", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Download dependencies if a user said so.
|
||||
if (downloadDependencies) {
|
||||
val runner = KonanCompilerRunner(project, project.konanExtension.jvmArgs)
|
||||
project.konanTargets.forEach {
|
||||
runner.run("-Xcheck_dependencies", "-target", it.visibleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanInteropLibrary
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanLibrary
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanProgram
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanArtifactsContainer
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import java.io.File
|
||||
|
||||
open class KonanGenerateCMakeTask : DefaultTask() {
|
||||
@Suppress("unused")
|
||||
@TaskAction
|
||||
fun generateCMake() {
|
||||
val interops = project.konanArtifactsContainer.toList().filterIsInstance<KonanInteropLibrary>()
|
||||
val libraries = project.konanArtifactsContainer.toList().filterIsInstance<KonanLibrary>()
|
||||
val programs = project.konanArtifactsContainer.toList().filterIsInstance<KonanProgram>()
|
||||
val cMakeLists = generateCMakeLists(
|
||||
project.name,
|
||||
interops,
|
||||
libraries,
|
||||
programs
|
||||
)
|
||||
File(project.projectDir, "CMakeLists.txt")
|
||||
.writeText(cMakeLists)
|
||||
|
||||
// This directory is filled out by the IDE
|
||||
File(project.projectDir, "KotlinCMakeModule")
|
||||
.mkdir()
|
||||
}
|
||||
|
||||
private val host = HostManager.host
|
||||
|
||||
private fun generateCMakeLists(
|
||||
projectName: String,
|
||||
interops: List<KonanInteropLibrary>,
|
||||
libraries: List<KonanLibrary>,
|
||||
programs: List<KonanProgram>
|
||||
): String {
|
||||
val cMakeCurrentListDir = "$" + "{CMAKE_CURRENT_LIST_DIR}"
|
||||
|
||||
return buildString {
|
||||
appendln("""
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
set(CMAKE_MODULE_PATH $cMakeCurrentListDir/KotlinCMakeModule)
|
||||
|
||||
project($projectName Kotlin)
|
||||
""".trimIndent())
|
||||
appendln()
|
||||
|
||||
for (interop in interops) {
|
||||
val task = interop[host] ?: continue
|
||||
appendln(
|
||||
Call("cinterop")
|
||||
.arg("NAME", interop.name)
|
||||
.arg("DEF_FILE", task.defFile.relativePath.toString().crossPlatformPath)
|
||||
.arg("COMPILER_OPTS", task.cMakeCompilerOpts)
|
||||
)
|
||||
}
|
||||
|
||||
for (library in libraries) {
|
||||
val task = library[host] ?: continue
|
||||
appendln(
|
||||
Call("konanc_library")
|
||||
.arg("NAME", library.name)
|
||||
.arg("SOURCES", task.cMakeSources)
|
||||
.arg("LIBRARIES", task.cMakeLibraries)
|
||||
.arg("LINKER_OPTS", task.cMakeLinkerOpts))
|
||||
}
|
||||
|
||||
for (program in programs) {
|
||||
val task = program[host] ?: continue
|
||||
appendln(
|
||||
Call("konanc_executable")
|
||||
.arg("NAME", program.name)
|
||||
.arg("SOURCES", task.cMakeSources)
|
||||
.arg("LIBRARIES", task.cMakeLibraries)
|
||||
.arg("LINKER_OPTS", task.cMakeLinkerOpts))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val File.relativePath get() = relativeTo(project.projectDir)
|
||||
|
||||
private val String.crossPlatformPath get() =
|
||||
if (host.family == Family.MINGW) replace('\\', '/') else this
|
||||
|
||||
private val FileCollection.asCMakeSourceList: List<String>
|
||||
get() = files.map { it.relativePath.toString().crossPlatformPath }
|
||||
|
||||
private val KonanInteropTask.cMakeCompilerOpts: String
|
||||
get() = (compilerOpts + includeDirs.allHeadersDirs.map { "-I${it.absolutePath.crossPlatformPath}" })
|
||||
.joinToString(" ")
|
||||
|
||||
private val KonanCompileTask.cMakeSources: String
|
||||
get() = allSources.flatMap { it.asCMakeSourceList }.joinToString(" ")
|
||||
|
||||
private val KonanCompileTask.cMakeLibraries: String
|
||||
get() = mutableListOf<String>().apply {
|
||||
addAll(libraries.artifacts.map { it.artifactName })
|
||||
addAll(libraries.namedKlibs)
|
||||
addAll(libraries.files.flatMap { it.files }.map { it.canonicalPath.crossPlatformPath })
|
||||
}.joinToString(" ")
|
||||
|
||||
private val KonanCompileTask.cMakeLinkerOpts: String
|
||||
get() = linkerOpts.joinToString(" ").replace('\\', '/')
|
||||
}
|
||||
|
||||
private class Call(val name: String) {
|
||||
private val args: MutableList<Pair<String, String>> = mutableListOf()
|
||||
|
||||
fun arg(key: String, value: String?): Call {
|
||||
if (value != null && value.isNotBlank()) args += key to value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
buildString {
|
||||
append(name)
|
||||
append("(")
|
||||
for ((key, value) in args) {
|
||||
appendln()
|
||||
append(" $key $value")
|
||||
}
|
||||
appendln(")")
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.tasks
|
||||
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.util.ConfigureUtil
|
||||
import org.gradle.workers.IsolationMode
|
||||
import org.gradle.workers.WorkAction
|
||||
import org.gradle.workers.WorkParameters
|
||||
import org.gradle.workers.WorkerExecutor
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanInteropSpec.IncludeDirectoriesSpec
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanModelArtifact
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanModelArtifactImpl
|
||||
import org.jetbrains.kotlin.konan.CURRENT
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.jetbrains.kotlin.konan.library.defaultResolver
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.Distribution
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A task executing cinterop tool with the given args and compiling the stubs produced by this tool.
|
||||
*/
|
||||
|
||||
open class KonanInteropTask @Inject constructor(@Internal val workerExecutor: WorkerExecutor) : KonanBuildingTask(), KonanInteropSpec {
|
||||
|
||||
@get:Internal
|
||||
override val toolRunner: KonanToolRunner = KonanInteropRunner(project, project.konanExtension.jvmArgs)
|
||||
|
||||
override fun init(config: KonanBuildingConfig<*>, destinationDir: File, artifactName: String, target: KonanTarget) {
|
||||
super.init(config, destinationDir, artifactName, target)
|
||||
this.defFile = project.konanDefaultDefFile(artifactName)
|
||||
}
|
||||
|
||||
// Output directories -----------------------------------------------------
|
||||
|
||||
override val artifactSuffix: String
|
||||
@Internal get() = ".klib"
|
||||
|
||||
override val artifactPrefix: String
|
||||
@Internal get() = ""
|
||||
|
||||
// Interop stub generator parameters -------------------------------------
|
||||
|
||||
@Internal var enableParallel: Boolean = false
|
||||
|
||||
@InputFile lateinit var defFile: File
|
||||
|
||||
@Optional @Input var packageName: String? = null
|
||||
|
||||
@Input val compilerOpts = mutableListOf<String>()
|
||||
@Input val linkerOpts = mutableListOf<String>()
|
||||
|
||||
@Nested val includeDirs = IncludeDirectoriesSpecImpl()
|
||||
|
||||
@InputFiles val headers = mutableSetOf<FileCollection>()
|
||||
@InputFiles val linkFiles = mutableSetOf<FileCollection>()
|
||||
|
||||
fun buildArgs() = mutableListOf<String>().apply {
|
||||
addArg("-o", artifact.canonicalPath)
|
||||
|
||||
addArgIfNotNull("-target", konanTarget.visibleName)
|
||||
addArgIfNotNull("-def", defFile.canonicalPath)
|
||||
addArgIfNotNull("-pkg", packageName)
|
||||
|
||||
addFileArgs("-header", headers)
|
||||
|
||||
compilerOpts.forEach {
|
||||
addArg("-compiler-option", it)
|
||||
}
|
||||
|
||||
val linkerOpts = mutableListOf<String>().apply { addAll(linkerOpts) }
|
||||
linkFiles.forEach {
|
||||
linkerOpts.addAll(it.files.map { it.canonicalPath })
|
||||
}
|
||||
linkerOpts.forEach {
|
||||
addArg("-linker-option", it)
|
||||
}
|
||||
|
||||
addArgs("-compiler-option", includeDirs.allHeadersDirs.map { "-I${it.absolutePath}" })
|
||||
addArgs("-headerFilterAdditionalSearchPrefix", includeDirs.headerFilterDirs.map { it.absolutePath })
|
||||
|
||||
addArgs("-repo", libraries.repos.map { it.canonicalPath })
|
||||
|
||||
addFileArgs("-library", libraries.files)
|
||||
addArgs("-library", libraries.namedKlibs)
|
||||
addArgs("-library", libraries.artifacts.map { it.artifact.canonicalPath })
|
||||
|
||||
addKey("-no-default-libs", noDefaultLibs)
|
||||
addKey("-no-endorsed-libs", noEndorsedLibs)
|
||||
|
||||
addAll(extraOpts)
|
||||
}
|
||||
|
||||
// region DSL.
|
||||
|
||||
inner class IncludeDirectoriesSpecImpl: IncludeDirectoriesSpec {
|
||||
@Input val allHeadersDirs = mutableSetOf<File>()
|
||||
@Input val headerFilterDirs = mutableSetOf<File>()
|
||||
|
||||
override fun allHeaders(vararg includeDirs: Any) = allHeaders(includeDirs.toList())
|
||||
override fun allHeaders(includeDirs: Collection<Any>) {
|
||||
allHeadersDirs.addAll(includeDirs.map { project.file(it) })
|
||||
}
|
||||
|
||||
override fun headerFilterOnly(vararg includeDirs: Any) = headerFilterOnly(includeDirs.toList())
|
||||
override fun headerFilterOnly(includeDirs: Collection<Any>) {
|
||||
headerFilterDirs.addAll(includeDirs.map { project.file(it) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun defFile(file: Any) {
|
||||
defFile = project.file(file)
|
||||
}
|
||||
|
||||
override fun packageName(value: String) {
|
||||
packageName = value
|
||||
}
|
||||
|
||||
override fun compilerOpts(vararg values: String) {
|
||||
compilerOpts.addAll(values)
|
||||
}
|
||||
|
||||
override fun header(file: Any) = headers(file)
|
||||
override fun headers(vararg files: Any) {
|
||||
headers.add(project.files(files))
|
||||
}
|
||||
override fun headers(files: FileCollection) {
|
||||
headers.add(files)
|
||||
}
|
||||
|
||||
override fun includeDirs(vararg values: Any) = includeDirs.allHeaders(values.toList())
|
||||
|
||||
override fun includeDirs(closure: Closure<Unit>) = includeDirs(ConfigureUtil.configureUsing(closure))
|
||||
override fun includeDirs(action: Action<IncludeDirectoriesSpec>) = includeDirs { action.execute(this) }
|
||||
override fun includeDirs(configure: IncludeDirectoriesSpec.() -> Unit) = includeDirs.configure()
|
||||
|
||||
override fun linkerOpts(vararg values: String) = linkerOpts(values.toList())
|
||||
override fun linkerOpts(values: List<String>) {
|
||||
linkerOpts.addAll(values)
|
||||
}
|
||||
|
||||
override fun link(vararg files: Any) {
|
||||
linkFiles.add(project.files(files))
|
||||
}
|
||||
override fun link(files: FileCollection) {
|
||||
linkFiles.add(files)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region IDE model
|
||||
override fun toModelArtifact(): KonanModelArtifact {
|
||||
val repos = libraries.repos
|
||||
val resolver = defaultResolver(
|
||||
repos.map { it.absolutePath },
|
||||
konanTarget,
|
||||
Distribution(project.konanHome)
|
||||
)
|
||||
|
||||
return KonanModelArtifactImpl(
|
||||
artifactName,
|
||||
artifact,
|
||||
CompilerOutputKind.LIBRARY,
|
||||
konanTarget.name,
|
||||
name,
|
||||
listOfNotNull(defFile.parentFile),
|
||||
listOf(defFile),
|
||||
libraries.asFiles(resolver),
|
||||
repos.toList()
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
||||
internal interface RunToolParameters: WorkParameters {
|
||||
var taskName: String
|
||||
var args: List<String>
|
||||
}
|
||||
|
||||
internal abstract class RunTool @Inject constructor() : WorkAction<RunToolParameters> {
|
||||
override fun execute() {
|
||||
val toolRunner = interchangeBox.remove(parameters.taskName) ?: error(":(")
|
||||
toolRunner.run(parameters.args)
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
destinationDir.mkdirs()
|
||||
if (dumpParameters) {
|
||||
dumpProperties(this)
|
||||
}
|
||||
val args = buildArgs()
|
||||
if (enableParallel) {
|
||||
val workQueue = workerExecutor.noIsolation()
|
||||
interchangeBox[this.path] = toolRunner
|
||||
workQueue.submit(RunTool::class.java) {
|
||||
it.taskName = this.path
|
||||
it.args = args
|
||||
}
|
||||
} else {
|
||||
toolRunner.run(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val interchangeBox = ConcurrentHashMap<String, KonanToolRunner>()
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.model
|
||||
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* An immutable representation of Konan's project model for gradle tooling API.
|
||||
* This model is shared with the client processes such as an IDE.
|
||||
*/
|
||||
interface KonanModel : Serializable {
|
||||
val konanHome: File
|
||||
val konanVersion: CompilerVersion
|
||||
val languageVersion: String?
|
||||
val apiVersion: String?
|
||||
|
||||
val artifacts: List<KonanModelArtifact>
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a single binary produced by Kotlin/Native for a cetrain target.
|
||||
*/
|
||||
interface KonanModelArtifact : Serializable {
|
||||
val name: String
|
||||
val type: CompilerOutputKind
|
||||
val targetPlatform: String
|
||||
val file: File
|
||||
val buildTaskName: String
|
||||
val srcDirs: List<File>
|
||||
val srcFiles: List<File>
|
||||
val libraries: List<File>
|
||||
val searchPaths: List<File>
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.model
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.tooling.provider.model.ToolingModelBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanArtifactsContainer
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanHome
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanVersion
|
||||
import org.jetbrains.kotlin.konan.CURRENT
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import java.io.File
|
||||
|
||||
|
||||
object KonanToolingModelBuilder : ToolingModelBuilder {
|
||||
|
||||
override fun canBuild(modelName: String) = KonanModel::class.java.name == modelName
|
||||
|
||||
private fun buildModelKonan(project: Project): KonanModel {
|
||||
val artifacts = project.konanArtifactsContainer.flatten().toList().map { it.toModelArtifact() }
|
||||
return KonanModelImpl(
|
||||
artifacts,
|
||||
project.file(project.konanHome),
|
||||
project.konanVersion,
|
||||
// TODO: Provide defaults for these versions.
|
||||
project.konanExtension.languageVersion,
|
||||
project.konanExtension.apiVersion
|
||||
)
|
||||
}
|
||||
|
||||
private val Project.hasKonanPlugin: Boolean
|
||||
get() = with(pluginManager) {
|
||||
hasPlugin("konan") ||
|
||||
hasPlugin("org.jetbrains.kotlin.konan")
|
||||
}
|
||||
|
||||
|
||||
override fun buildAll(modelName: String, project: Project): KonanModel =
|
||||
when {
|
||||
project.hasKonanPlugin -> buildModelKonan(project)
|
||||
else -> throw IllegalStateException("The project '${project.path}' has no Kotlin/Native plugin")
|
||||
}
|
||||
}
|
||||
|
||||
internal data class KonanModelImpl(
|
||||
override val artifacts: List<KonanModelArtifact>,
|
||||
override val konanHome: File,
|
||||
override val konanVersion: CompilerVersion,
|
||||
override val languageVersion: String?,
|
||||
override val apiVersion: String?
|
||||
) : KonanModel
|
||||
|
||||
internal data class KonanModelArtifactImpl(
|
||||
override val name: String,
|
||||
override val file: File,
|
||||
override val type: CompilerOutputKind,
|
||||
override val targetPlatform: String,
|
||||
override val buildTaskName: String,
|
||||
override val srcDirs: List<File>,
|
||||
override val srcFiles: List<File>,
|
||||
override val libraries: List<File>,
|
||||
override val searchPaths: List<File>
|
||||
) : KonanModelArtifact
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright 2010-2017 JetBrains s.r.o.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
shadow.org.jetbrains.kotlinx.serialization.gradle.SerializationKotlinGradleSubplugin
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import spock.lang.Specification
|
||||
|
||||
class BaseKonanSpecification extends Specification {
|
||||
|
||||
@Rule
|
||||
TemporaryFolder tmpFolder = new TemporaryFolder()
|
||||
File getProjectDirectory() { return tmpFolder.root }
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
|
||||
class DefaultSpecification extends BaseKonanSpecification {
|
||||
|
||||
def 'Plugin should build a project without additional settings'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.buildFile.write("""
|
||||
plugins { id 'konan' }
|
||||
konanArtifacts {
|
||||
interop('stdio')
|
||||
library('main')
|
||||
}
|
||||
""".stripIndent())
|
||||
it.generateDefFile("stdio.def", "")
|
||||
it.generateSrcFile("main.kt")
|
||||
}
|
||||
def result = project.createRunner().withArguments('build').build()
|
||||
|
||||
|
||||
then:
|
||||
!result.tasks.collect { it.outcome }.contains(TaskOutcome.FAILED)
|
||||
}
|
||||
}
|
||||
+336
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Unroll
|
||||
|
||||
import static org.jetbrains.kotlin.gradle.plugin.test.KonanProject.escapeBackSlashes
|
||||
|
||||
// TODO: Rewrite tests using Kotlin.
|
||||
|
||||
class EnvVariableSpecification extends BaseKonanSpecification {
|
||||
|
||||
class WrapperResult {
|
||||
|
||||
private int exitValue;
|
||||
private String stdout;
|
||||
private String stderr;
|
||||
|
||||
WrapperResult(Process process) {
|
||||
exitValue = process.exitValue()
|
||||
stdout = process.getInputStream().readLines().join("\n")
|
||||
stderr = process.getErrorStream().readLines().join("\n")
|
||||
}
|
||||
|
||||
int getExitValue() { return exitValue }
|
||||
String getStdout() { return stdout }
|
||||
String getStderr() { return stderr }
|
||||
|
||||
WrapperResult printStdout() { println(stdout); return this }
|
||||
WrapperResult printStderr() { println(stderr); return this }
|
||||
}
|
||||
|
||||
private KonanProject createProjectWithWrapper() {
|
||||
def project = KonanProject.createEmpty(projectDirectory)
|
||||
def runner = project.createRunner()
|
||||
|
||||
// Gradle TestKit doesn't support setting environment variables for runners.
|
||||
// So we use the following hack: we create a gradle wrapper, start it as a separate
|
||||
// process with custom environment variables and check its exit code and output.
|
||||
runner.withArguments("wrapper").build()
|
||||
|
||||
def classpath = runner.pluginClasspath.collect { "'${escapeBackSlashes(it.absolutePath)}'" }.join(", ")
|
||||
project.buildFile.write("""\
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath files($classpath)
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
return project
|
||||
}
|
||||
|
||||
private WrapperResult runWrapper(KonanProject project,
|
||||
List<String> tasks,
|
||||
Map<String, String> environment = [:],
|
||||
Map<String, String> properties = ["konan.useEnvironmentVariables": 'true']) {
|
||||
def wrapper = (HostManager.host.family == Family.MINGW) ? "gradlew.bat" : "gradlew"
|
||||
def command = ["$project.projectDir.absolutePath/$wrapper".toString()]
|
||||
command.addAll(tasks)
|
||||
command.addAll(properties.collect { "-P${it.key}=${it.value}".toString() })
|
||||
def projectBuilder = new ProcessBuilder()
|
||||
.directory(project.projectDir)
|
||||
.command(command)
|
||||
projectBuilder.environment().putAll(environment)
|
||||
def process = projectBuilder.start()
|
||||
process.waitFor()
|
||||
return new WrapperResult(process)
|
||||
}
|
||||
|
||||
private WrapperResult runWrapper(KonanProject project,
|
||||
String task,
|
||||
Map<String, String> environment = [:],
|
||||
Map<String, String> properties = ["konan.useEnvironmentVariables": 'true']) {
|
||||
return runWrapper(project, [task], environment, properties)
|
||||
}
|
||||
|
||||
private String artifactFileName(String baseName, ArtifactType type, KonanTarget target = HostManager.host) {
|
||||
String suffix = ""
|
||||
String prefix = ""
|
||||
switch (type) {
|
||||
case ArtifactType.PROGRAM:
|
||||
suffix = target.family.exeSuffix
|
||||
break
|
||||
case ArtifactType.INTEROP:
|
||||
case ArtifactType.LIBRARY:
|
||||
suffix = "klib"
|
||||
break
|
||||
case ArtifactType.BITCODE:
|
||||
suffix = "bc"
|
||||
break;
|
||||
case ArtifactType.DYNAMIC:
|
||||
prefix = target.family.dynamicPrefix
|
||||
suffix = target.family.dynamicSuffix
|
||||
break
|
||||
case ArtifactType.STATIC:
|
||||
prefix = target.family.staticPrefix
|
||||
suffix = target.family.staticSuffix
|
||||
break
|
||||
case ArtifactType.FRAMEWORK:
|
||||
suffix = "framework"
|
||||
}
|
||||
return "$prefix${baseName}.$suffix"
|
||||
}
|
||||
|
||||
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
|
||||
@Unroll("Plugin should support #action via an env variable")
|
||||
def 'Plugin should support enabling/disabling debug/opt via an env variable'() {
|
||||
when:
|
||||
def project = createProjectWithWrapper()
|
||||
project.buildFile.append("""\
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
library('main')
|
||||
}
|
||||
|
||||
task assertEnableDebug {
|
||||
doLast {
|
||||
konanArtifacts.main.forEach {
|
||||
if (!($assertion)) throw new AssertionError("$message for \${it.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
def result = runWrapper(project,"assertEnableDebug", [(variable): value])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
|
||||
then:
|
||||
result == 0
|
||||
|
||||
where:
|
||||
action |variable |value |assertion |message
|
||||
"enabling debug" |"DEBUGGING_SYMBOLS" |"YES" |"it.enableDebug" |"Debug should be enabled"
|
||||
"disabling debug" |"DEBUGGING_SYMBOLS" |"NO" |"!it.enableDebug" |"Debug should be disabled"
|
||||
"enabling opt" |"KONAN_ENABLE_OPTIMIZATIONS" |"YES" |"it.enableOptimizations" |"Opts should be enabled"
|
||||
"disabling opt" |"KONAN_ENABLE_OPTIMIZATIONS" |"NO" |"!it.enableOptimizations" |"Opts should be disabled"
|
||||
}
|
||||
|
||||
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
|
||||
def 'Plugin should support setting destination directory via an env variable'() {
|
||||
when:
|
||||
def project = createProjectWithWrapper()
|
||||
def newDestinationDir = project.createSubDir("newDestination")
|
||||
def newDestinationPath = newDestinationDir.absolutePath
|
||||
project.buildFile.append("""\
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
program('program')
|
||||
library('library')
|
||||
dynamic('dynamic')
|
||||
framework('framework')
|
||||
}
|
||||
|
||||
task assertDestinationDir {
|
||||
doLast {
|
||||
konanArtifacts.forEach { artifact ->
|
||||
artifact.forEach {
|
||||
if (it.destinationDir.absolutePath != '${escapeBackSlashes(newDestinationPath)}'){
|
||||
throw new AssertionError("Unexpected destination dir for \$it.name\\n" +
|
||||
"expected: ${escapeBackSlashes(newDestinationPath)}\\n" +
|
||||
"actual: \$it.destinationDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
def assertResult = runWrapper(project, "assertDestinationDir", ["CONFIGURATION_BUILD_DIR": newDestinationPath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def buildResult = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": newDestinationPath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def files = newDestinationDir.list()
|
||||
|
||||
then:
|
||||
assertResult == 0
|
||||
buildResult == 0
|
||||
files.contains(artifactFileName("program", ArtifactType.PROGRAM))
|
||||
files.contains(artifactFileName("library", ArtifactType.LIBRARY))
|
||||
files.contains(artifactFileName("dynamic", ArtifactType.DYNAMIC))
|
||||
files.contains(artifactFileName("static", ArtifactType.STATIC))
|
||||
if (HostManager.hostIsMac) {
|
||||
files.contains(artifactFileName("framework", ArtifactType.FRAMEWORK))
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
|
||||
def 'Plugin should rerun tasks if CONFIGURATION_BUILD_DIR has been changed'() {
|
||||
when:
|
||||
def project = createProjectWithWrapper()
|
||||
def destination1 = project.createSubDir("destination1", "subdir")
|
||||
def destination2 = project.createSubDir("destination2", "subdir")
|
||||
project.buildFile.append("""\
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
library('main')
|
||||
}
|
||||
""".stripIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
|
||||
def buildResult1 = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": destination1.absolutePath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def buildResult2 = runWrapper(project, "build", ["CONFIGURATION_BUILD_DIR": destination2.absolutePath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def files1 = destination1.list()
|
||||
def files2 = destination2.list()
|
||||
|
||||
then:
|
||||
buildResult1 == 0
|
||||
buildResult2 == 0
|
||||
destination1.exists()
|
||||
destination2.exists()
|
||||
files1.contains(artifactFileName("main", ArtifactType.LIBRARY))
|
||||
files2.contains(artifactFileName("main", ArtifactType.LIBRARY))
|
||||
}
|
||||
|
||||
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
|
||||
def 'Plugin should ignore environmentVariables if konan.useEnvironmentVariables is false or is not set'() {
|
||||
when:
|
||||
def project = createProjectWithWrapper()
|
||||
def newDestinationDir = project.createSubDir("newDestination")
|
||||
def newDestinationPath = newDestinationDir.absolutePath
|
||||
project.buildFile.append("""\
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
program('program')
|
||||
library('library')
|
||||
dynamic('dynamic')
|
||||
framework('framework')
|
||||
}
|
||||
|
||||
task assertNoOverrides {
|
||||
doLast {
|
||||
konanArtifacts.forEach { artifact ->
|
||||
artifact.forEach {
|
||||
if (it.destinationDir.absolutePath == '${escapeBackSlashes(newDestinationPath)}'){
|
||||
throw new AssertionError("CONFIGURATION_BUILD_DIR overrides a default output path " +
|
||||
"when it shouldn't.\\n" +
|
||||
"Task: \${it.name}, Path: \${it.destinationDir}")
|
||||
}
|
||||
|
||||
if (it.enableDebug) {
|
||||
throw new AssertionError("DEBUGGING_SYMBOLS overrides a default value " +
|
||||
"when it shouldn't\\n" +
|
||||
"Task: \${it.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
def resultNoProp = runWrapper(project,
|
||||
"assertNoOverrides",
|
||||
["DEBUGGING_SYMBOLS": "true", "CONFIGURATION_BUILD_DIR": newDestinationPath], [:])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def resultFalseValue = runWrapper(project,
|
||||
"assertNoOverrides",
|
||||
["DEBUGGING_SYMBOLS": "true", "CONFIGURATION_BUILD_DIR": newDestinationPath],
|
||||
["konan.useEnvironmentVariables": "false"])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
|
||||
then:
|
||||
resultNoProp == 0
|
||||
resultFalseValue == 0
|
||||
}
|
||||
|
||||
@Ignore("The plugin doesn't use env vars until https://github.com/gradle/gradle/issues/3468 is fixed.")
|
||||
def 'Up-to-date checks should work with different directories for different targets'() {
|
||||
when:
|
||||
def project = createProjectWithWrapper()
|
||||
def fooDir = project.createSubDir("foo")
|
||||
def barDir = project.createSubDir("bar")
|
||||
project.buildFile.append("""\
|
||||
apply plugin: 'konan'
|
||||
|
||||
konanArtifacts {
|
||||
library('foo')
|
||||
library('bar')
|
||||
}
|
||||
|
||||
task assertUpToDate {
|
||||
dependsOn 'compileKonanFoo'
|
||||
doLast {
|
||||
if (!konanArtifacts.foo.getByTarget('host').state.upToDate) {
|
||||
throw new AssertionError("Compilation task is not up-to-date")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
|
||||
def buildResult1 = runWrapper(project, "compileKonanFoo",
|
||||
["CONFIGURATION_BUILD_DIR": fooDir.absolutePath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def buildResult2 = runWrapper(project, "compileKonanBar",
|
||||
["CONFIGURATION_BUILD_DIR": barDir.absolutePath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
def buildResult3 = runWrapper(project,
|
||||
"assertUpToDate",
|
||||
["CONFIGURATION_BUILD_DIR": fooDir.absolutePath])
|
||||
.printStderr()
|
||||
.getExitValue()
|
||||
|
||||
then:
|
||||
buildResult1 == 0
|
||||
buildResult2 == 0
|
||||
buildResult3 == 0
|
||||
}
|
||||
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import spock.lang.Unroll
|
||||
|
||||
class IncrementalSpecification extends BaseKonanSpecification {
|
||||
|
||||
Tuple buildTwice(KonanProject project, String task = 'build', Closure change) {
|
||||
def runner = project.createRunner().withArguments(task)
|
||||
def firstResult = runner.build()
|
||||
change(project)
|
||||
def secondResult = runner.build()
|
||||
return new Tuple(project, firstResult, secondResult)
|
||||
}
|
||||
|
||||
Tuple buildTwice(ArtifactType mainArtifactType = ArtifactType.LIBRARY, String task = 'build', Closure change) {
|
||||
return buildTwice(KonanProject.createWithInterop(projectDirectory, mainArtifactType), change)
|
||||
}
|
||||
|
||||
Boolean noRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
|
||||
return project.with {
|
||||
firstResult.tasks.collect { it.path }.containsAll(buildingTasks) &&
|
||||
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
|
||||
secondResult.taskPaths(TaskOutcome.UP_TO_DATE).containsAll(buildingTasks) &&
|
||||
firstResult.task(downloadTask).outcome == TaskOutcome.SUCCESS &&
|
||||
secondResult.task(downloadTask).outcome == TaskOutcome.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
Boolean onlyRecompilationHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
|
||||
return project.with {
|
||||
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
|
||||
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(compilationTasks) &&
|
||||
secondResult.taskPaths(TaskOutcome.UP_TO_DATE).containsAll(interopTasks)
|
||||
}
|
||||
}
|
||||
|
||||
Boolean recompilationAndInteropProcessingHappened(KonanProject project, BuildResult firstResult, BuildResult secondResult) {
|
||||
return project.with {
|
||||
firstResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks) &&
|
||||
secondResult.taskPaths(TaskOutcome.SUCCESS).containsAll(buildingTasks)
|
||||
}
|
||||
}
|
||||
|
||||
//region tests =====================================================================================================
|
||||
def 'Compilation is up-to-date if there is no changes'() {
|
||||
when:
|
||||
def results = buildTwice {}
|
||||
|
||||
then:
|
||||
noRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
def 'Source change should cause only recompilation'() {
|
||||
when:
|
||||
def results = buildTwice { KonanProject project ->
|
||||
project.srcFiles[0].append("\n // Some change in the source file")
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
|
||||
}
|
||||
|
||||
def 'Def-file change should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def results = buildTwice { KonanProject project ->
|
||||
project.defFiles[0].append("\n # Some change in the def-file")
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
}
|
||||
|
||||
@Unroll("#parameter change for a compilation task should cause only recompilation")
|
||||
def 'Parameter changes should cause only recompilaton'() {
|
||||
when:
|
||||
def results = buildTwice { KonanProject project ->
|
||||
project.addSetting("main", parameter, value)
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
|
||||
|
||||
where:
|
||||
parameter | value
|
||||
"baseDir" | "'build/new/outputDir'"
|
||||
"enableOptimizations" | "true"
|
||||
"linkerOpts" | "'--help'"
|
||||
"enableAssertions" | "true"
|
||||
"enableDebug" | "true"
|
||||
"artifactName" | "'foo'"
|
||||
"extraOpts" | "'-Xtime'"
|
||||
"noDefaultLibs" | "true"
|
||||
"noEndorsedLibs" | "true"
|
||||
}
|
||||
|
||||
def 'Plugin should support a custom entry point and recompile an artifact if it changes'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("main", """
|
||||
|fun main(args: Array<String>) { println("default main") }
|
||||
|
|
||||
""".stripMargin())
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.srcFiles[0].write("""
|
||||
|package foo
|
||||
|
|
||||
|fun bar(args: Array<String>) { println("changed main") }
|
||||
|
|
||||
""".stripMargin())
|
||||
it.addSetting("main", "entryPoint", "'foo.bar'")
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
def 'srcFiles change for a compilation task should cause only recompilation'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
|
||||
it.generateSrcFile(["src", "foo", "kotlin"], 'bar.kt', """
|
||||
fun foo(args: Array<String>) { println("Hello!") }
|
||||
""".stripIndent())
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting("main", "srcFiles", "project.fileTree('src/foo/kotlin')")
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
def 'Library change for a compilation task should cause only recompilation'() {
|
||||
when:
|
||||
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
|
||||
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('lib') {
|
||||
srcFiles fileTree('src/lib/kotlin')
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addLibraryToArtifact("main", 'lib')
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
def 'Native library change for a compilation task should cause only recompilaton'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
|
||||
it.generateSrcFile(["src", "lib", "kotlin"], "lib.kt", "fun bar() { println(\"Hello!\") }")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
bitcode('lib') {
|
||||
srcFiles fileTree('src/lib/kotlin')
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting("main", "nativeLibrary", "compileKonanLib${KonanProject.HOST.capitalize()}.artifact")
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
// TODO: Test library for incremental compilation.
|
||||
|
||||
@Unroll("#parameter change for an interop task should cause recompilation and interop reprocessing")
|
||||
def 'Parameter change for an interop task should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def results = buildTwice { KonanProject project ->
|
||||
project.addSetting("stdio", parameter, value)
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
|
||||
where:
|
||||
parameter | value
|
||||
"packageName" | "'org.sample'"
|
||||
"compilerOpts" | "'-g'"
|
||||
"linkerOpts" | "'--help'"
|
||||
"includeDirs" | "'src'"
|
||||
"includeDirs.allHeaders" | "'src'"
|
||||
"extraOpts" | "'-verbose'"
|
||||
"noDefaultLibs" | "true"
|
||||
"noEndorsedLibs" | "true"
|
||||
}
|
||||
|
||||
def 'includeDirs.headerFilterOnly change should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory) { KonanProject it ->
|
||||
it.defFiles.first().write("headers = stdio.h\nheaderFilter = stdio.h")
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting(KonanProject.DEFAULT_INTEROP_NAME, "includeDirs.headerFilterOnly", "'.'")
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
}
|
||||
|
||||
def 'defFile change for an interop task should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
|
||||
def defFile = project.generateDefFile("foo.def", "#some content")
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting("stdio", "defFile", defFile)
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
}
|
||||
|
||||
def 'header change for an interop task should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
|
||||
def header = project.generateSrcFile('header.h', "#define CONST 1")
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting("stdio", "headers", header)
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
}
|
||||
|
||||
def 'link change for an interop task should cause recompilation and interop reprocessing'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
|
||||
it.generateSrcFile(["src", "lib", "kotlin"], 'lib.kt', 'fun foo() { println(42) }')
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
bitcode('lib') {
|
||||
srcFiles fileTree('src/lib/kotlin')
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def results = buildTwice(project) { KonanProject it ->
|
||||
it.addSetting("stdio", "dependsOn", "konanArtifacts.lib.${KonanProject.HOST}")
|
||||
it.addSetting("stdio", "link", "files(konanArtifacts.lib.${KonanProject.HOST}.artifactPath)")
|
||||
}
|
||||
|
||||
then:
|
||||
recompilationAndInteropProcessingHappened(*results)
|
||||
}
|
||||
|
||||
def 'Common source change should cause recompilation'() {
|
||||
when:
|
||||
File commonSource
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = MultiplatformSpecification.createCommonProject(it)
|
||||
commonSource = MultiplatformSpecification.createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
println(it.settingsFile.text)
|
||||
|
||||
it.generateSrcFile("actual.kt", "actual fun foo() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
it.buildingTasks.addAll([":compileKonanFoo", ":compileKonanFoo${KonanProject.HOST}}"])
|
||||
}
|
||||
|
||||
def results = buildTwice(project, ':build') { KonanProject ->
|
||||
commonSource.append("\nfun bar() = 43")
|
||||
}
|
||||
|
||||
then:
|
||||
onlyRecompilationHappened(*results)
|
||||
}
|
||||
|
||||
// TODO: Add incremental tests for the 'libraries' block.
|
||||
|
||||
//endregion
|
||||
}
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.GradleRunner
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
enum ArtifactType {
|
||||
PROGRAM("program"),
|
||||
LIBRARY("library"),
|
||||
BITCODE("bitcode"),
|
||||
INTEROP("interop"),
|
||||
DYNAMIC("dynamic"),
|
||||
STATIC("static"),
|
||||
FRAMEWORK("framework")
|
||||
|
||||
String type
|
||||
ArtifactType(String type) { this.type = type }
|
||||
String toString() { return type }
|
||||
}
|
||||
|
||||
class KonanProject {
|
||||
|
||||
static String DEFAULT_ARTIFACT_NAME = 'main'
|
||||
static String DEFAULT_INTEROP_NAME = "stdio"
|
||||
|
||||
static String HOST = HostManager.hostName
|
||||
|
||||
File projectDir
|
||||
Path projectPath
|
||||
File konanBuildDir
|
||||
|
||||
String konanHome
|
||||
String gradleVersion
|
||||
|
||||
File buildFile
|
||||
File propertiesFile
|
||||
File settingsFile
|
||||
|
||||
Set<File> srcFiles = []
|
||||
Set<File> defFiles = []
|
||||
|
||||
List<String> interopTasks = []
|
||||
List<String> compilationTasks = []
|
||||
String downloadTask = ":checkKonanCompiler"
|
||||
|
||||
List<String> targets
|
||||
|
||||
List<String> getBuildingTasks() { return compilationTasks + interopTasks }
|
||||
List<String> getKonanTasks() { return getBuildingTasks() + downloadTask }
|
||||
|
||||
static String DEFAULT_SRC_CONTENT = """
|
||||
fun main(args: Array<String>) {
|
||||
println(42)
|
||||
}
|
||||
"""
|
||||
|
||||
static String DEFAULT_DEF_CONTENT = """
|
||||
headers = stdio.h
|
||||
""".stripIndent()
|
||||
|
||||
protected KonanProject(File projectDir){
|
||||
this(projectDir, [HOST])
|
||||
}
|
||||
|
||||
protected KonanProject(File projectDir, List<String> targets) {
|
||||
this.projectDir = projectDir
|
||||
this.targets = targets
|
||||
projectPath = projectDir.toPath()
|
||||
konanBuildDir = projectPath.resolve('build/konan').toFile()
|
||||
def konanHomeDir = new File(getKonanHome())
|
||||
if (!konanHomeDir.exists() || !konanHomeDir.directory) {
|
||||
throw new IllegalStateException("konan.home doesn't exist or is not a directory: $konanHomeDir.canonicalPath")
|
||||
}
|
||||
// Escape windows path separator
|
||||
this.konanHome = escapeBackSlashes(konanHomeDir.canonicalPath)
|
||||
this.gradleVersion = System.getProperty("gradleVersion") ?: GradleVersion.current().version
|
||||
}
|
||||
|
||||
GradleRunner createRunner(boolean withDebug = true) {
|
||||
return GradleRunner.create()
|
||||
.withProjectDir(projectDir)
|
||||
.withPluginClasspath()
|
||||
.withDebug(withDebug)
|
||||
.withGradleVersion(gradleVersion)
|
||||
}
|
||||
|
||||
/** Creates a subdirectory specified by the given path. */
|
||||
File createSubDir(String ... path) {
|
||||
return createSubDir(Paths.get(*path))
|
||||
}
|
||||
|
||||
/** Creates a subdirectory specified by the given path. */
|
||||
File createSubDir(Path path) {
|
||||
return Files.createDirectories(projectPath.resolve(path)).toFile()
|
||||
}
|
||||
|
||||
/** Creates a file with the given content in project subdirectory specified by parentDirectory. */
|
||||
File createFile(Path parentDirectory = projectPath, String fileName, String content) {
|
||||
def parent = projectPath.resolve(parentDirectory)
|
||||
Files.createDirectories(parent)
|
||||
def result = parent.resolve(fileName).toFile()
|
||||
result.createNewFile()
|
||||
result.write(content)
|
||||
return result
|
||||
}
|
||||
|
||||
/** Creates a file with the given content in project subdirectory specified by parentPath. */
|
||||
File createFile(List<String> parentPath, String fileName, String content) {
|
||||
return createFile(Paths.get(*parentPath), fileName, content)
|
||||
}
|
||||
|
||||
/** Creates a folder for project source files (src/main/kotlin). */
|
||||
void generateFolders() {
|
||||
createSubDir("src", "main", "kotlin")
|
||||
createSubDir("src", "main", "c_interop")
|
||||
}
|
||||
|
||||
/** Generates a build.gradle file in the root project directory with the given content. */
|
||||
File generateBuildFile(String content) {
|
||||
buildFile = createFile(projectPath, "build.gradle", content)
|
||||
return buildFile
|
||||
}
|
||||
|
||||
/** Generates a settings.gradle file in the root project directory with the given content. */
|
||||
File generateSettingsFile(String content) {
|
||||
settingsFile = createFile(projectPath, "settings.gradle", content)
|
||||
return settingsFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a build.gradle file in root project directory with the default content (see below)
|
||||
* and fills the compilationTasks array.
|
||||
*
|
||||
* plugins { id 'konan' }
|
||||
*
|
||||
* konanArtifacts {
|
||||
* program('$DEFAULT_ARTIFACT_NAME')
|
||||
* }
|
||||
*/
|
||||
File generateBuildFile() {
|
||||
def result = generateBuildFile("""
|
||||
|plugins { id 'konan' }
|
||||
|
|
||||
|konan.targets = [${targets.collect { "'$it'" }.join(", ")}]
|
||||
|""".stripMargin()
|
||||
)
|
||||
compilationTasks = [":compileKonan", ":build"]
|
||||
return result
|
||||
}
|
||||
|
||||
/** Generates a source file with the given name and content in the given directory and adds it into srcFiles */
|
||||
File generateSrcFile(Path parentDirectory, String fileName, String content) {
|
||||
def result = createFile(parentDirectory, fileName, content)
|
||||
srcFiles.add(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/** Generates a source file with the given name and content in the given directory and adds it into srcFiles */
|
||||
File generateSrcFile(List<String> parentPath, String fileName, String content) {
|
||||
return generateSrcFile(Paths.get(*parentPath), fileName, content)
|
||||
}
|
||||
|
||||
/** Generates a source file with the given name and content in 'src/main/kotlin' and adds it into srcFiles */
|
||||
File generateSrcFile(String fileName, String content) {
|
||||
return generateSrcFile(["src", "main", "kotlin"], fileName, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a source file with the given name and default content (see below) in src/main/kotlin
|
||||
* and adds it into srcFiles.
|
||||
*
|
||||
* fun main(args: Array<String>) {
|
||||
* println(42)
|
||||
* }
|
||||
*/
|
||||
File generateSrcFile(String fileName) {
|
||||
return generateSrcFile(fileName, DEFAULT_SRC_CONTENT)
|
||||
}
|
||||
|
||||
/** Creates a def-file with the given name and content in src/main/c_interop directory and adds it to defFiles. */
|
||||
File generateDefFile(String fileName, String content) {
|
||||
def result = createFile(["src", "main", "c_interop"], fileName, content)
|
||||
defFiles.add(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a def-file with the given name and the default content (see below) in src/main/c_interop directory
|
||||
* and adds it to defFiles.
|
||||
*
|
||||
* headers = stdio.h stdlib.h string.h
|
||||
*/
|
||||
File generateDefFile(String fileName = "${DEFAULT_INTEROP_NAME}.def") {
|
||||
return generateDefFile(fileName, DEFAULT_DEF_CONTENT)
|
||||
}
|
||||
|
||||
/** Generates gradle.properties file with the konan.home and konan.jvmArgs properties set. */
|
||||
File generatePropertiesFile(String konanHome, String konanJvmArgs = System.getProperty("konan.jvmArgs") ?: "") {
|
||||
propertiesFile = createFile(projectPath, "gradle.properties", """\
|
||||
org.jetbrains.kotlin.native.home=$konanHome
|
||||
${!konanJvmArgs.isEmpty() ? "konan.jvmArgs=$konanJvmArgs\n" : ""}
|
||||
""".stripIndent())
|
||||
return propertiesFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given setting of the given project extension.
|
||||
* In other words adds the following string in the build file:
|
||||
*
|
||||
* $container.$section.$parameter $value
|
||||
*/
|
||||
protected void addSetting(String container, String section, String parameter, String value) {
|
||||
buildFile.append("$container.$section.$parameter $value\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given setting of the given project extension using the path of the file as a value.
|
||||
* In other words adds the following string in the build file:
|
||||
*
|
||||
* $container.$section.$parameter ${value.canonicalPath.replace(\, \\)}
|
||||
*/
|
||||
protected void addSetting(String container, String section, String parameter, File value) {
|
||||
addSetting(container, section, parameter, "'${escapeBackSlashes(value.canonicalPath)}'")
|
||||
}
|
||||
|
||||
/** Sets the given setting of the given konanArtifact */
|
||||
void addSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, String value) {
|
||||
addSetting("konanArtifacts", artifactName, parameter, value)
|
||||
}
|
||||
|
||||
/** Sets the given setting of the given konanArtifact using the path of the file as a value. */
|
||||
void addSetting(String artifactName = DEFAULT_ARTIFACT_NAME, String parameter, File value) {
|
||||
addSetting("konanArtifacts", artifactName, parameter, value)
|
||||
}
|
||||
|
||||
void addLibraryToArtifact(String artifactName = DEFAULT_ARTIFACT_NAME, String library = DEFAULT_INTEROP_NAME) {
|
||||
addLibraryToArtifactCustom(artifactName, "artifact '$library'")
|
||||
}
|
||||
|
||||
void addLibraryToArtifactCustom(String artifactName = DEFAULT_ARTIFACT_NAME, String closureContent) {
|
||||
buildFile.append("konanArtifacts.${artifactName}.libraries { $closureContent }\n")
|
||||
}
|
||||
|
||||
/** Returns the path of compileKonan... task for the default artifact. */
|
||||
static String defaultCompilationTask(String target = HOST) {
|
||||
return compilationTask(DEFAULT_ARTIFACT_NAME, target)
|
||||
}
|
||||
|
||||
static String defaultInteropTask(String target = HOST) {
|
||||
return compilationTask(DEFAULT_INTEROP_NAME, target)
|
||||
}
|
||||
|
||||
/** Returns the path of compileKonan... task for the artifact specified. */
|
||||
static String compilationTask(String artifactName, String target = HOST) {
|
||||
return ":compileKonan${artifactName.capitalize()}${target.capitalize()}"
|
||||
}
|
||||
|
||||
static String defaultCompilationConfig() {
|
||||
return artifactConfig(DEFAULT_ARTIFACT_NAME)
|
||||
}
|
||||
|
||||
static String defaultInteropConfig() {
|
||||
return artifactConfig(DEFAULT_INTEROP_NAME)
|
||||
}
|
||||
|
||||
static String artifactConfig(String artifactName) {
|
||||
return "konanArtifacts.$artifactName"
|
||||
}
|
||||
|
||||
static String outputAccessCode(String artifact, String target = HOST) {
|
||||
return "${artifactConfig(artifact)}.${target}.artifact"
|
||||
}
|
||||
|
||||
void addCompilerArtifact(String name, String content = "", ArtifactType type = ArtifactType.PROGRAM) {
|
||||
def newTasks = targets.collect { compilationTask(name, it) } + ":compileKonan${name.capitalize()}".toString()
|
||||
buildFile.append("konanArtifacts { $type('$name') }\n")
|
||||
if (type == ArtifactType.INTEROP) {
|
||||
defFiles += generateDefFile("${name}.def", content)
|
||||
interopTasks += newTasks
|
||||
} else {
|
||||
def src = generateSrcFile(projectPath.resolve("src/$name/kotlin"), "source.kt", content)
|
||||
addSetting(name, "srcFiles", src)
|
||||
srcFiles += src
|
||||
compilationTasks += newTasks
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Creates a project with default build and source files. */
|
||||
static KonanProject create(File projectDir,
|
||||
ArtifactType artifactType = ArtifactType.PROGRAM,
|
||||
List<String> targets = [HOST]) {
|
||||
return createEmpty(projectDir, targets) { KonanProject p ->
|
||||
p.addCompilerArtifact(DEFAULT_ARTIFACT_NAME, DEFAULT_SRC_CONTENT, artifactType)
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a project with default build and source files. */
|
||||
static KonanProject create(File projectDir,
|
||||
ArtifactType artifactType = ArtifactType.PROGRAM,
|
||||
List<String> targets = [HOST],
|
||||
Closure config) {
|
||||
def result = create(projectDir, artifactType, targets)
|
||||
config(result)
|
||||
return result
|
||||
}
|
||||
|
||||
static KonanProject createWithInterop(File projectDir,
|
||||
ArtifactType mainArtifactType = ArtifactType.PROGRAM,
|
||||
List<String> targets = [HOST]) {
|
||||
return create(projectDir, mainArtifactType, targets) { KonanProject p ->
|
||||
p.addCompilerArtifact(DEFAULT_INTEROP_NAME, DEFAULT_DEF_CONTENT, ArtifactType.INTEROP)
|
||||
p.addLibraryToArtifact()
|
||||
}
|
||||
}
|
||||
|
||||
static KonanProject createWithInterop(File projectDir,
|
||||
ArtifactType mainArtifactType = ArtifactType.PROGRAM,
|
||||
List<String> targets = [HOST],
|
||||
Closure config) {
|
||||
def result = createWithInterop(projectDir, mainArtifactType, targets)
|
||||
config(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/** Creates a project with the default build file and without any source files. */
|
||||
static KonanProject createEmpty(File projectDir, List<String> targets = [HOST]) {
|
||||
def result = new KonanProject(projectDir, targets)
|
||||
result.with {
|
||||
generateFolders()
|
||||
generateBuildFile()
|
||||
generatePropertiesFile(konanHome)
|
||||
generateSettingsFile("")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Creates a project with the default build file and without any source files. */
|
||||
static KonanProject createEmpty(File projectDir, List<String> targets = [HOST], Closure config) {
|
||||
def result = createEmpty(projectDir, targets)
|
||||
config(result)
|
||||
return result
|
||||
}
|
||||
|
||||
static String escapeBackSlashes(String value) {
|
||||
return value.replace('\\', '\\\\')
|
||||
}
|
||||
|
||||
static String getKonanHome() {
|
||||
def konanHome = System.getProperty("konan.home") ?: System.getProperty("org.jetbrains.kotlin.native.home")
|
||||
if (konanHome == null) {
|
||||
throw new IllegalStateException("konan.home isn't specified")
|
||||
}
|
||||
return konanHome
|
||||
}
|
||||
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
class LibrarySpecification extends BaseKonanSpecification {
|
||||
|
||||
def libraries = [
|
||||
[manualDependsOn: true , code: { l1, l2 ->
|
||||
"file ${KonanProject.outputAccessCode(l1)}\nfile ${KonanProject.outputAccessCode(l2)}"
|
||||
}],
|
||||
[manualDependsOn: true , code: { l1, l2 ->
|
||||
"files ${KonanProject.outputAccessCode(l1)}, ${KonanProject.outputAccessCode(l2)}"
|
||||
}],
|
||||
[manualDependsOn: true , code: { l1, l2 ->
|
||||
"files project.files(${KonanProject.outputAccessCode(l1)}, ${KonanProject.outputAccessCode(l2)})"
|
||||
}],
|
||||
[manualDependsOn: true , code: { l1, l2 -> "klib '$l1'\nklib '$l2'" }],
|
||||
[manualDependsOn: true , code: { l1, l2 -> "klibs '$l1', '$l2'" }],
|
||||
[manualDependsOn: false, code: { l1, l2 -> "artifact '$l1'\nartifact '$l2'" }],
|
||||
[manualDependsOn: false, code: { l1, l2 -> "artifact konanArtifacts.$l1\nartifact konanArtifacts.$l2" }],
|
||||
]
|
||||
|
||||
String createMainWithCalls(List<Tuple2<String, String>> functions, Closure<String> callBuilder) {
|
||||
def result = new StringBuilder("""
|
||||
|fun main(args: Array<String>) {\n
|
||||
""".stripMargin())
|
||||
|
||||
functions.forEach {
|
||||
result.append(callBuilder(it.first))
|
||||
result.append(callBuilder(it.second))
|
||||
}
|
||||
|
||||
result.append("}")
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
void createLibraryWithFunction(KonanProject project, String name) {
|
||||
project.addCompilerArtifact(name, """
|
||||
|package $name
|
||||
|
|
||||
|fun $name() {
|
||||
| println("$name")
|
||||
|}
|
||||
""".stripMargin(), ArtifactType.LIBRARY)
|
||||
project.addSetting(name, "noDefaultLibs", "true")
|
||||
project.addSetting(name, "noEndorsedLibs", "true")
|
||||
}
|
||||
|
||||
void createInteropLibrary(KonanProject project, String name) {
|
||||
project.addCompilerArtifact(name, "headers = math.h", ArtifactType.INTEROP)
|
||||
project.addSetting(name, "noDefaultLibs", "true")
|
||||
project.addSetting(name, "noEndorsedLibs", "true")
|
||||
}
|
||||
|
||||
KonanProject createProjectWithLibraries(Closure createLibraryFunction, Closure callBuilder) {
|
||||
def result = KonanProject.createEmpty(projectDirectory)
|
||||
|
||||
def libraryNames = new ArrayList<Tuple2<String, String>>()
|
||||
for (int i = 0; i < libraries.size(); i++) {
|
||||
libraryNames.add(new Tuple2("foo$i", "bar$i"))
|
||||
}
|
||||
|
||||
libraryNames.forEach {
|
||||
createLibraryFunction(result, it.first)
|
||||
createLibraryFunction(result, it.second)
|
||||
}
|
||||
|
||||
result.addCompilerArtifact("main", createMainWithCalls(libraryNames, callBuilder))
|
||||
result.addSetting("main", "noDefaultLibs", "true")
|
||||
result.addSetting("main", "noEndorsedLibs", "true")
|
||||
|
||||
for (int i = 0; i < libraries.size(); i++) {
|
||||
def foo = libraryNames[i].first
|
||||
def bar = libraryNames[i].second
|
||||
result.addLibraryToArtifactCustom("main", libraries[i].code(foo, bar) )
|
||||
|
||||
if (libraries[i].manualDependsOn) {
|
||||
result.addSetting("main", "dependsOn", "konanArtifacts.$foo")
|
||||
result.addSetting("main", "dependsOn", "konanArtifacts.$bar")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
KonanProject createProjectWithSimpleLibraries() {
|
||||
return createProjectWithLibraries(
|
||||
{ p, n -> createLibraryWithFunction(p, n) },
|
||||
{ "$it.$it()\n" } )
|
||||
}
|
||||
|
||||
|
||||
KonanProject createProjectWithInteropLibraries() {
|
||||
return createProjectWithLibraries(
|
||||
{ p, n -> createInteropLibrary(p, n) },
|
||||
{ "println(${it}.cos(0.0))\n" }
|
||||
)
|
||||
}
|
||||
|
||||
def 'Plugin should support libraries from the same project'() {
|
||||
expect:
|
||||
createProjectWithSimpleLibraries()
|
||||
.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support interop libraries from the same project'() {
|
||||
expect:
|
||||
createProjectWithInteropLibraries()
|
||||
.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support allLibrariesFrom method for the current project'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("foo", "noDefaultLibs", "true")
|
||||
it.addSetting("foo", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("bar", "noDefaultLibs", "true")
|
||||
it.addSetting("bar", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
|
||||
it.addSetting("main", "noDefaultLibs", "true" )
|
||||
it.addSetting("main", "noEndorsedLibs", "true" )
|
||||
it.addLibraryToArtifactCustom("main", "allLibrariesFrom project")
|
||||
}
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support allLibrariesFrom method for another project'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory)
|
||||
def subproject = KonanProject.createEmpty(project.createSubDir("subproject")) { KonanProject it ->
|
||||
it.buildFile.write("apply plugin: 'konan'\n")
|
||||
}
|
||||
project.settingsFile.append("include ':subproject'")
|
||||
|
||||
project.addCompilerArtifact("wrongFoo","fun foo() { println(24) }", ArtifactType.LIBRARY)
|
||||
project.addSetting("wrongFoo", "noDefaultLibs", "true")
|
||||
project.addSetting("wrongFoo", "noEndorsedLibs", "true")
|
||||
|
||||
subproject.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
subproject.addSetting("foo", "noDefaultLibs", "true")
|
||||
subproject.addSetting("foo", "noEndorsedLibs", "true")
|
||||
|
||||
subproject.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
|
||||
subproject.addSetting("bar", "noDefaultLibs", "true")
|
||||
subproject.addSetting("bar", "noEndorsedLibs", "true")
|
||||
|
||||
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
|
||||
project.addSetting("main", "noDefaultLibs", "true" )
|
||||
project.addSetting("main", "noEndorsedLibs", "true" )
|
||||
project.addLibraryToArtifactCustom("main", "allLibrariesFrom project('subproject')")
|
||||
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support allInteropLibrariesFrom method for the current project'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory)
|
||||
def subproject = KonanProject.createEmpty(project.createSubDir("subproject")) { KonanProject it ->
|
||||
it.buildFile.write("apply plugin: 'konan'\n")
|
||||
}
|
||||
project.settingsFile.append("include ':subproject'")
|
||||
|
||||
|
||||
project.addCompilerArtifact("wrongFoo1", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
project.addSetting("wrongFoo1", "noDefaultLibs", "true")
|
||||
project.addSetting("wrongFoo1", "noEndorsedLibs", "true")
|
||||
|
||||
subproject.addCompilerArtifact("wrongFoo2", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
subproject.addSetting("wrongFoo2", "noDefaultLibs", "true")
|
||||
subproject.addSetting("wrongFoo2", "noEndorsedLibs", "true")
|
||||
|
||||
subproject.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
|
||||
subproject.addSetting("math1", "noDefaultLibs", "true")
|
||||
subproject.addSetting("math1", "noEndorsedLibs", "true")
|
||||
|
||||
subproject.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
|
||||
subproject.addSetting("math2", "noDefaultLibs", "true")
|
||||
subproject.addSetting("math2", "noEndorsedLibs", "true")
|
||||
|
||||
project.addCompilerArtifact("main" ,"""
|
||||
|fun foo() {}
|
||||
|
|
||||
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
|
||||
""".stripMargin())
|
||||
project.addSetting("main", "noDefaultLibs", "true" )
|
||||
project.addSetting("main", "noEndorsedLibs", "true" )
|
||||
project.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project('subproject')")
|
||||
|
||||
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support allInteropLibrariesFrom method for another projecct'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("wrongFoo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("wrongFoo", "noDefaultLibs", "true")
|
||||
it.addSetting("wrongFoo", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
|
||||
it.addSetting("math1", "noDefaultLibs", "true")
|
||||
it.addSetting("math1", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
|
||||
it.addSetting("math2", "noDefaultLibs", "true")
|
||||
it.addSetting("math2", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("main" ,"""
|
||||
|fun foo() {}
|
||||
|
|
||||
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
|
||||
""".stripMargin())
|
||||
it.addSetting("main", "noDefaultLibs", "true" )
|
||||
it.addSetting("main", "noEndorsedLibs", "true" )
|
||||
it.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project")
|
||||
}
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support custom repositories for libraries'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("foo", "noDefaultLibs", "true")
|
||||
it.addSetting("foo", "noEndorsedLibs", "true")
|
||||
it.addSetting("foo", "baseDir", "file('out')")
|
||||
|
||||
|
||||
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo() }")
|
||||
it.addSetting("main", "noDefaultLibs", "true" )
|
||||
it.addSetting("main", "noEndorsedLibs", "true" )
|
||||
it.addSetting("main", "dependsOn", "konanArtifacts.foo.$KonanProject.HOST")
|
||||
it.addLibraryToArtifactCustom("main", "klib 'foo'")
|
||||
it.addLibraryToArtifactCustom("main", "useRepo 'out/$KonanProject.HOST'")
|
||||
}
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"), "-i")
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support library dependencies in the same project'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("foo", "noDefaultLibs", "true")
|
||||
it.addSetting("foo", "noEndorsedLibs", "true")
|
||||
|
||||
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
|
||||
it.addSetting("bar", "noDefaultLibs", "true")
|
||||
it.addSetting("bar", "noEndorsedLibs", "true")
|
||||
it.addLibraryToArtifact("bar", "foo")
|
||||
|
||||
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
|
||||
it.addSetting("main", "noDefaultLibs", "true" )
|
||||
it.addSetting("main", "noEndorsedLibs", "true" )
|
||||
it.addLibraryToArtifact("main", "bar")
|
||||
}
|
||||
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"), "-i")
|
||||
.build()
|
||||
}
|
||||
|
||||
def 'Plugin should support library dependencies from other projects'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory)
|
||||
def subproject1 = KonanProject.createEmpty(project.createSubDir("subproject1")) { KonanProject it ->
|
||||
it.buildFile.write("apply plugin: 'konan'\n")
|
||||
}
|
||||
def subproject2 = KonanProject.createEmpty(project.createSubDir("subproject2")) { KonanProject it ->
|
||||
it.buildFile.write("apply plugin: 'konan'\n")
|
||||
}
|
||||
project.settingsFile.append("include ':subproject1'\ninclude ':subproject2'")
|
||||
|
||||
subproject1.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
|
||||
subproject1.addSetting("foo", "noDefaultLibs", "true")
|
||||
subproject1.addSetting("foo", "noEndorsedLibs", "true")
|
||||
|
||||
subproject2.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
|
||||
subproject2.addSetting("bar", "noDefaultLibs", "true")
|
||||
subproject2.addSetting("bar", "noEndorsedLibs", "true")
|
||||
subproject2.addLibraryToArtifactCustom(
|
||||
"bar", "artifact rootProject.project('subproject1'), 'foo'"
|
||||
)
|
||||
|
||||
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
|
||||
project.addSetting("main", "noDefaultLibs", "true" )
|
||||
project.addSetting("main", "noEndorsedLibs", "true" )
|
||||
project.addLibraryToArtifactCustom(
|
||||
"main", "artifact project('subproject2'), 'bar'"
|
||||
)
|
||||
|
||||
project.createRunner()
|
||||
.withArguments(KonanProject.compilationTask("main"))
|
||||
.build()
|
||||
}
|
||||
|
||||
// TODO: Add tests for incorrect cases (e.g. attempt to use an executable as a library)
|
||||
}
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import spock.lang.Ignore
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
class MultiplatformSpecification extends BaseKonanSpecification {
|
||||
|
||||
public static final String KOTLIN_VERSION = System.getProperty("kotlin.version")
|
||||
public static final String KOTLIN_REPO = System.getProperty("kotlin.repo")
|
||||
public static final String DEFAULT_COMMON_BUILD_FILE_CONTENT = """\
|
||||
buildscript {
|
||||
repositories {
|
||||
maven {
|
||||
url = '$KOTLIN_REPO'
|
||||
}
|
||||
maven {
|
||||
url = 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-platform-common'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = '$KOTLIN_REPO'
|
||||
}
|
||||
maven {
|
||||
url = 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-common:$KOTLIN_VERSION"
|
||||
}
|
||||
""".stripIndent()
|
||||
|
||||
static File createCommonProject(KonanProject platformProject,
|
||||
String commonProjectName = "common",
|
||||
String commonBuildFileContent = DEFAULT_COMMON_BUILD_FILE_CONTENT) {
|
||||
def commonDirectory = platformProject.createSubDir(commonProjectName)
|
||||
def commonBuildFile = Paths.get(commonDirectory.absolutePath, "build.gradle")
|
||||
commonBuildFile.write(commonBuildFileContent)
|
||||
platformProject.settingsFile.append("include ':$commonProjectName'\n")
|
||||
return commonDirectory
|
||||
}
|
||||
|
||||
static File createCommonSource(File commonDirectory,
|
||||
Iterable<String> subdirectory,
|
||||
String fileName,
|
||||
String content) {
|
||||
def commonSrcDir = commonDirectory.toPath().resolve(Paths.get(*subdirectory))
|
||||
def commonSource = commonSrcDir.resolve(fileName)
|
||||
Files.createDirectories(commonSrcDir)
|
||||
commonSource.write(content)
|
||||
return commonSource.toFile()
|
||||
}
|
||||
|
||||
def 'Plugin should support multiplatform projects'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"""\
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE_ERROR")
|
||||
@OptionalExpectation
|
||||
expect annotation class Optional()
|
||||
|
||||
@Optional
|
||||
fun opt() = 42
|
||||
|
||||
expect fun foo(): Int
|
||||
""".stripIndent()
|
||||
)
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Multiplatform projects should be disabled by default'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "fun foo() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Plugin should use the \'main\' source set as a default common source set'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
|
||||
sourceSets {
|
||||
common.kotlin.srcDir 'src/common/kotlin'
|
||||
}
|
||||
""".stripIndent())
|
||||
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "common", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun bar(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Plugin should allow a user to specify custom common source set'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
|
||||
sourceSets {
|
||||
common.kotlin.srcDir 'src/common/kotlin'
|
||||
}
|
||||
""".stripIndent())
|
||||
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "common", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun bar(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
commonSourceSets 'common'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Plugin should allow setting several common source sets'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
|
||||
sourceSets {
|
||||
common.kotlin.srcDir 'src/common/kotlin'
|
||||
}
|
||||
""".stripIndent())
|
||||
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "common", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun bar(): Int")
|
||||
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"main.kt",
|
||||
"expect fun foo() : Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun bar() = 42\nactual fun foo() = 43")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
commonSourceSets 'common', 'main'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Build should fail if the expectedBy dependency is not a project one'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun foo() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy files('common/src/main/kotlin/common.kt')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments(":build").buildAndFail()
|
||||
|
||||
then:
|
||||
result.output.contains("dependency is not a project: ")
|
||||
}
|
||||
|
||||
def 'Build should support several expectedBy-dependencies'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it, "commonFoo")
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
|
||||
commonDirectory = createCommonProject(it, "commonBar")
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun bar(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun foo() = 0\nactual fun bar() = 0")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':commonFoo')
|
||||
expectedBy project(':commonBar')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
|
||||
def 'Build should fail if the common project has no common plugin'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it,"common", "")
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments(":build").buildAndFail()
|
||||
|
||||
then:
|
||||
result.output.contains("has an 'expectedBy' dependency to non-common project")
|
||||
}
|
||||
|
||||
@Ignore("TODO in the Big Kotlin plugin")
|
||||
def 'Build should fail if custom common source set doesn\'t exist'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "main", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun foo(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
enableMultiplatform true
|
||||
commonSourceSets 'common'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments(":build").buildAndFail()
|
||||
|
||||
then:
|
||||
result.output.contains("Cannot find a source set with name 'common' in a common project")
|
||||
}
|
||||
|
||||
def 'Setting custom source set should enable the multiplatform support'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
def commonDirectory = createCommonProject(it)
|
||||
Paths.get(commonDirectory.absolutePath, "build.gradle").append("""
|
||||
sourceSets {
|
||||
common.kotlin.srcDir 'src/common/kotlin'
|
||||
}
|
||||
""".stripIndent())
|
||||
|
||||
createCommonSource(commonDirectory,
|
||||
["src", "common", "kotlin"],
|
||||
"common.kt",
|
||||
"expect fun bar(): Int")
|
||||
|
||||
it.generateSrcFile("platform.kt", "actual fun bar() = 42")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
commonSourceSets 'common'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
expectedBy project(':common')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments(":build").build()
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import org.jetbrains.kotlin.konan.target.Distribution
|
||||
import org.jetbrains.kotlin.konan.target.PlatformManager
|
||||
|
||||
class PathSpecification extends BaseKonanSpecification {
|
||||
|
||||
boolean fileExists(KonanProject project, String path) {
|
||||
project.konanBuildDir.toPath().resolve(path).toFile().exists()
|
||||
}
|
||||
|
||||
def platformManager = new PlatformManager(new Distribution(KonanProject.konanHome, false, null), false)
|
||||
|
||||
def 'Plugin should provide a correct path to the artifacts created'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(
|
||||
projectDirectory,
|
||||
platformManager.filteredOutEnabledButNotSupported.collect { t -> t.visibleName }
|
||||
) { KonanProject it ->
|
||||
it.generateSrcFile("main.kt")
|
||||
it.generateDefFile("interop.def", "")
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
program('program')
|
||||
library('library')
|
||||
bitcode('bitcode')
|
||||
interop('interop')
|
||||
framework('framework')
|
||||
dynamic('dynamic')
|
||||
}
|
||||
|
||||
task checkArtifacts(type: DefaultTask) {
|
||||
dependsOn(':build')
|
||||
doLast {
|
||||
for(artifact in konanArtifacts) {
|
||||
for (target in artifact) {
|
||||
if (!target.artifact.exists()) throw new Exception("Artifact doesn't exist. Type: \${artifact.name}, target: \${target.target}")
|
||||
}
|
||||
}
|
||||
for (target in konanArtifacts.dynamic) {
|
||||
if (!target.headerFile.exists()) throw new Exception("Header file doesn't exist. Target: \${target.target}")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
project.createRunner().withArguments("checkArtifacts").build()
|
||||
|
||||
}
|
||||
|
||||
def 'Plugin should create all necessary directories'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory)
|
||||
project.addCompilerArtifact("lib", "fun foo() {}", ArtifactType.LIBRARY)
|
||||
project.addCompilerArtifact("bit", "fun bar() {}", ArtifactType.BITCODE)
|
||||
project.createRunner().withArguments('build').build()
|
||||
|
||||
then:
|
||||
project.konanBuildDir.toPath().resolve("bin/$KonanProject.HOST").toFile().listFiles().findAll {
|
||||
File it -> it.file && it.name.matches("^${KonanProject.DEFAULT_ARTIFACT_NAME}\\.[^.]+")
|
||||
}.size() > 0
|
||||
|
||||
fileExists(project, "libs/$KonanProject.HOST/${KonanProject.DEFAULT_INTEROP_NAME}.klib")
|
||||
fileExists(project, "libs/$KonanProject.HOST/lib.klib")
|
||||
fileExists(project, "bitcode/$KonanProject.HOST/bit.bc")
|
||||
}
|
||||
|
||||
def 'Plugin should stop building if the compiler classpath is empty'() {
|
||||
when:
|
||||
def project = KonanProject.create(projectDirectory)
|
||||
project.propertiesFile.write("konan.home=fakepath")
|
||||
def result = project.createRunner().withArguments('build').buildAndFail()
|
||||
def task = result.task(project.defaultCompilationTask())
|
||||
|
||||
then:
|
||||
task == null || task.outcome == TaskOutcome.FAILED
|
||||
}
|
||||
|
||||
def 'Plugin should stop building if the stub generator classpath is empty'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory)
|
||||
project.propertiesFile.write("konan.home=fakepath")
|
||||
def result = project.createRunner().withArguments('build').buildAndFail()
|
||||
def task = result.task(project.compilationTask(KonanProject.DEFAULT_INTEROP_NAME))
|
||||
|
||||
then:
|
||||
task == null || task.outcome == TaskOutcome.FAILED
|
||||
}
|
||||
|
||||
def 'Plugin should remove custom output directories'() {
|
||||
when:
|
||||
def customOutputDir = projectDirectory.toPath().resolve("foo").toFile()
|
||||
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY) { KonanProject it ->
|
||||
it.addSetting("baseDir", customOutputDir)
|
||||
}
|
||||
|
||||
def res1 = project.createRunner().withArguments("build").build()
|
||||
def artifactExistsAfterBuild = customOutputDir.toPath()
|
||||
.resolve("${KonanProject.HOST}/${KonanProject.DEFAULT_ARTIFACT_NAME}.klib").toFile()
|
||||
.exists()
|
||||
|
||||
def res2 = project.createRunner().withArguments("clean").build()
|
||||
def artifactExistsAfterClean = customOutputDir.toPath()
|
||||
.resolve("${KonanProject.HOST}/${KonanProject.DEFAULT_ARTIFACT_NAME}.klib").toFile()
|
||||
.exists()
|
||||
|
||||
then:
|
||||
res1.taskPaths(TaskOutcome.SUCCESS).containsAll(project.buildingTasks)
|
||||
res2.taskPaths(TaskOutcome.SUCCESS).contains(":clean")
|
||||
artifactExistsAfterBuild
|
||||
!artifactExistsAfterClean
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
|
||||
class RegressionSpecification extends BaseKonanSpecification {
|
||||
|
||||
def 'KT-19916'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(getProjectDirectory()) { KonanProject prj ->
|
||||
prj.generateSettingsFile("include ':subproject'")
|
||||
def subprojectDir = prj.projectPath.resolve("subproject").toFile()
|
||||
subprojectDir.mkdirs()
|
||||
subprojectDir.toPath().resolve("build.gradle").write("""
|
||||
dependencies {
|
||||
libs gradleApi()
|
||||
}
|
||||
""".stripIndent())
|
||||
|
||||
prj.buildFile.append("""
|
||||
subprojects {
|
||||
apply plugin: 'konan'
|
||||
apply plugin: Foo
|
||||
}
|
||||
|
||||
class Foo implements Plugin<Project> {
|
||||
void apply(Project project) {
|
||||
project.configurations.maybeCreate("libs")
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
|
||||
def result = project.createRunner().withArguments('tasks').build()
|
||||
|
||||
then:
|
||||
result.task(':tasks').outcome == TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
// Ensure gradle plugin fails in case of linker errors.
|
||||
def 'KT-20192'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(getProjectDirectory()) { KonanProject prj ->
|
||||
prj.addCompilerArtifact(KonanProject.DEFAULT_ARTIFACT_NAME,"""
|
||||
external fun foo()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
foo()
|
||||
}
|
||||
""", ArtifactType.PROGRAM)
|
||||
}
|
||||
def result = project.createRunner().withArguments('build').buildAndFail()
|
||||
|
||||
then:
|
||||
result.taskPaths(TaskOutcome.FAILED).contains(project.defaultCompilationTask())
|
||||
}
|
||||
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import spock.lang.Requires
|
||||
import spock.lang.Unroll
|
||||
|
||||
class TaskSpecification extends BaseKonanSpecification {
|
||||
|
||||
def 'Configs should allow user to add dependencies to them'() {
|
||||
when:
|
||||
def project = KonanProject.createWithInterop(projectDirectory, ArtifactType.LIBRARY)
|
||||
project.buildFile.append("""
|
||||
task beforeInterop(type: DefaultTask) { doLast { println("Before Interop") } }
|
||||
task beforeCompilation(type: DefaultTask) { doLast { println("Before compilation") } }
|
||||
""".stripIndent())
|
||||
project.addSetting(KonanProject.DEFAULT_INTEROP_NAME,"dependsOn", "beforeInterop")
|
||||
project.addSetting("dependsOn", "beforeCompilation")
|
||||
def result = project.createRunner().withArguments('build').build()
|
||||
|
||||
then:
|
||||
def beforeInterop = result.task(":beforeInterop")
|
||||
beforeInterop != null && beforeInterop.outcome == TaskOutcome.SUCCESS
|
||||
def beforeCompilation = result.task(":beforeCompilation")
|
||||
beforeCompilation != null && beforeCompilation.outcome == TaskOutcome.SUCCESS
|
||||
}
|
||||
|
||||
def 'Compiler should print time measurements if measureTime flag is set'() {
|
||||
when:
|
||||
def project = KonanProject.create(projectDirectory, ArtifactType.LIBRARY)
|
||||
project.addSetting("measureTime", "true")
|
||||
def result = project.createRunner().withArguments('build').build()
|
||||
|
||||
then:
|
||||
result.output.findAll(~/Frontend builds AST:\s+\d+\s+msec/).size() == 1
|
||||
result.output.findAll(~/IR Lowering:\s+\d+\s+msec/).size() == 1
|
||||
}
|
||||
|
||||
@Unroll('Plugin should support #option option for cinterop')
|
||||
def 'Plugin should support includeDir option for cinterop'() {
|
||||
expect:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.addCompilerArtifact("interopLib", "headers=foo.h\n$headerFilter", ArtifactType.INTEROP)
|
||||
it.generateSrcFile(it.projectPath, "foo.h", "#include <bar.h>")
|
||||
def fooDir = it.projectPath.resolve("foo")
|
||||
it.generateSrcFile(fooDir, "bar.h", "const int foo = 5;")
|
||||
it.addSetting("interopLib", option, fooDir.toFile())
|
||||
it.addSetting("interopLib", option, it.projectDir)
|
||||
}
|
||||
project.createRunner().withArguments("build").build()
|
||||
|
||||
where:
|
||||
option | headerFilter
|
||||
"includeDirs.headerFilterOnly" | "headerFilter=foo.h bar.h"
|
||||
"includeDirs.allHeaders" | ""
|
||||
"includeDirs" | ""
|
||||
}
|
||||
|
||||
@Requires({ HostManager.host instanceof KonanTarget.MACOS_X64 })
|
||||
def 'Plugin should create framework tasks only for Apple targets'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.buildFile.append("""
|
||||
konan.targets = ['wasm32', 'macbook', 'iphone', 'iphone_sim']
|
||||
|
||||
konanArtifacts {
|
||||
framework('foo')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments('tasks', '--all').build()
|
||||
|
||||
then:
|
||||
!compilationTaskExists(result,'foo', 'wasm32')
|
||||
compilationTaskExists (result,'foo', 'macbook')
|
||||
compilationTaskExists (result,'foo', 'iphone')
|
||||
compilationTaskExists (result,'foo', 'iphone_sim')
|
||||
}
|
||||
|
||||
def 'Plugin should support different targets for different artifacts'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory, ['host']) { KonanProject it ->
|
||||
it.buildFile.append("""
|
||||
konanArtifacts {
|
||||
program('defaultTarget')
|
||||
program('customTarget', targets: ['wasm32'])
|
||||
program('customTargets', targets: ['host', 'wasm32'])
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments('tasks', '--all').build()
|
||||
def hostName = HostManager.hostName
|
||||
|
||||
then:
|
||||
compilationTaskExists (result, 'defaultTarget', hostName)
|
||||
!compilationTaskExists(result, 'defaultTarget', 'wasm32')
|
||||
!compilationTaskExists(result, 'customTarget', hostName)
|
||||
compilationTaskExists (result, 'customTarget', 'wasm32')
|
||||
compilationTaskExists (result, 'customTargets', hostName)
|
||||
compilationTaskExists (result, 'customTargets', 'wasm32')
|
||||
}
|
||||
|
||||
def 'Plugin should not create dynamic task for wasm'() {
|
||||
when:
|
||||
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
|
||||
it.buildFile.append("""
|
||||
konan.targets = ['wasm32']
|
||||
|
||||
konanArtifacts {
|
||||
dynamic('foo')
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
def result = project.createRunner().withArguments('tasks', '--all').build()
|
||||
|
||||
then:
|
||||
!compilationTaskExists(result, 'foo', 'wasm32')
|
||||
}
|
||||
|
||||
|
||||
boolean taskExists(BuildResult result, String taskName) {
|
||||
def taskNameForSearch = taskName.startsWith(':') ? taskName.substring(1) : taskName
|
||||
return result.output =~ "\\s$taskNameForSearch\\s"
|
||||
}
|
||||
|
||||
boolean compilationTaskExists(BuildResult result, String artifactName, String targetName) {
|
||||
return taskExists(result, KonanProject.compilationTask(artifactName, targetName))
|
||||
}
|
||||
|
||||
BuildResult failOnPropertyAccess(KonanProject project, String property) {
|
||||
project.buildFile.append("""
|
||||
task testTask(type: DefaultTask) {
|
||||
doLast {
|
||||
println(${project.defaultInteropConfig()}.$property)
|
||||
}
|
||||
}
|
||||
""".stripIndent())
|
||||
return project.createRunner().withArguments("testTask").buildAndFail()
|
||||
}
|
||||
|
||||
BuildResult failOnTaskAccess(KonanProject project, String task) {
|
||||
project.buildFile.append("""
|
||||
task testTask(type: DefaultTask) {
|
||||
dependsOn $task
|
||||
}
|
||||
""".stripIndent())
|
||||
return project.createRunner().withArguments("testTask").buildAndFail()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
open class CompatibilityTests {
|
||||
val tmpFolder = TemporaryFolder()
|
||||
@Rule get
|
||||
|
||||
val projectDirectory: File
|
||||
get() = tmpFolder.root
|
||||
|
||||
@Test
|
||||
fun `Plugin should fail if running with Gradle prior to the required one`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory)
|
||||
val result = project
|
||||
.createRunner()
|
||||
.withGradleDistribution(URI.create(
|
||||
"https://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-4.5-bin.zip"
|
||||
))
|
||||
.withArguments("tasks")
|
||||
.buildAndFail()
|
||||
println(result.output)
|
||||
assertTrue("Build doesn't show the warning message") {
|
||||
result.output.contains("Kotlin/Native Gradle plugin is incompatible with this version of Gradle.")
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.test.KonanProject.escapeBackSlashes
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.tools4j.spockito.Spockito
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
|
||||
@RunWith(Spockito::class)
|
||||
open class PropertiesAsEnvVariablesTest {
|
||||
|
||||
val tmpFolder = TemporaryFolder()
|
||||
@Rule get
|
||||
|
||||
val projectDirectory: File
|
||||
get() = tmpFolder.root
|
||||
|
||||
private fun artifactFileName(baseName: String, type: ArtifactType, target: KonanTarget = HostManager.host): String {
|
||||
var suffix = ""
|
||||
var prefix = ""
|
||||
when (type) {
|
||||
ArtifactType.PROGRAM -> suffix = target.family.exeSuffix
|
||||
ArtifactType.INTEROP,
|
||||
ArtifactType.LIBRARY -> suffix = "klib"
|
||||
ArtifactType.BITCODE -> suffix = "bc"
|
||||
ArtifactType.FRAMEWORK -> suffix = "framework"
|
||||
ArtifactType.DYNAMIC -> {
|
||||
prefix = target.family.dynamicPrefix
|
||||
suffix = target.family.dynamicSuffix
|
||||
}
|
||||
ArtifactType.STATIC -> {
|
||||
prefix = target.family.staticPrefix
|
||||
suffix = target.family.staticSuffix
|
||||
}
|
||||
|
||||
}
|
||||
return "$prefix${baseName}.$suffix"
|
||||
}
|
||||
|
||||
private fun assertFileExists(directory: File, filename: String) = assert(directory.list().contains(filename)) {
|
||||
"No such file: $filename in directory: ${directory.absolutePath}"
|
||||
}
|
||||
|
||||
@Test
|
||||
@Spockito.Unroll(
|
||||
"|property |value |assertion |message |",
|
||||
"|konan.debugging.symbols |YES |it.enableDebug |Debug should be enabled |",
|
||||
"|konan.debugging.symbols |true |it.enableDebug |Debug should be enabled |",
|
||||
"|konan.debugging.symbols |NO |!it.enableDebug |Debug should be disabled |",
|
||||
"|konan.debugging.symbols |false |!it.enableDebug |Debug should be disabled |",
|
||||
"|konan.optimizations.enable |YES |it.enableOptimizations |Opts should be enabled |",
|
||||
"|konan.optimizations.enable |true |it.enableOptimizations |Opts should be enabled |",
|
||||
"|konan.optimizations.enable |NO |!it.enableOptimizations |Opts should be disabled |",
|
||||
"|konan.optimizations.enable |false |!it.enableOptimizations |Opts should be disabled |"
|
||||
)
|
||||
@Spockito.Name("[{row}]: {variable}={value}")
|
||||
fun `Plugin should support enabling and disabling debug and opt options via a project property`(
|
||||
property: String,
|
||||
value: String,
|
||||
assertion: String,
|
||||
message: String
|
||||
) {
|
||||
val project = KonanProject.createEmpty(projectDirectory)
|
||||
project.buildFile.appendText("""
|
||||
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
library('main')
|
||||
}
|
||||
|
||||
task assertEnableDebug {
|
||||
doLast {
|
||||
konanArtifacts.main.forEach {
|
||||
if (!($assertion)) throw new AssertionError("$message for ${'$'}it.name")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
project.createRunner()
|
||||
.withArguments("assertEnableDebug", "-P${property}=${value}")
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Plugin should support setting destination directory via a project property`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory)
|
||||
val newDestinationDir = project.createSubDir("newDestination")
|
||||
val newDestinationPath = newDestinationDir.absolutePath
|
||||
project.buildFile.appendText("""
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
program('program')
|
||||
library('library')
|
||||
dynamic('dynamic')
|
||||
framework('framework')
|
||||
}
|
||||
|
||||
task assertDestinationDir {
|
||||
doLast {
|
||||
konanArtifacts.forEach { artifact ->
|
||||
artifact.forEach {
|
||||
if (it.destinationDir.absolutePath != '${escapeBackSlashes(newDestinationPath)}'){
|
||||
throw new AssertionError("Unexpected destination dir for ${'$'}it.name\\n" +
|
||||
"expected: ${escapeBackSlashes(newDestinationPath)}\\n" +
|
||||
"actual: ${'$'}it.destinationDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
project.createRunner()
|
||||
.withArguments("assertDestinationDir", "build", "-Pkonan.configuration.build.dir=$newDestinationPath")
|
||||
.build()
|
||||
|
||||
assertFileExists(newDestinationDir, artifactFileName("program", ArtifactType.PROGRAM))
|
||||
assertFileExists(newDestinationDir, artifactFileName("library", ArtifactType.LIBRARY))
|
||||
assertFileExists(newDestinationDir, artifactFileName("dynamic", ArtifactType.DYNAMIC))
|
||||
if (HostManager.hostIsMac) {
|
||||
assertFileExists(newDestinationDir, artifactFileName("framework", ArtifactType.FRAMEWORK))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Plugin should rerun tasks if konan_configuration_build_dir has been changed`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory)
|
||||
val destination1 = project.createSubDir("destination1", "subdir")
|
||||
val destination2 = project.createSubDir("destination2", "subdir")
|
||||
|
||||
project.buildFile.appendText("""
|
||||
apply plugin: 'konan'
|
||||
konanArtifacts {
|
||||
library('main')
|
||||
}
|
||||
""".trimIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
|
||||
project.createRunner()
|
||||
.withArguments("build", "-Pkonan.configuration.build.dir=${destination1.absolutePath}")
|
||||
.build()
|
||||
project.createRunner()
|
||||
.withArguments("build", "-Pkonan.configuration.build.dir=${destination2.absolutePath}")
|
||||
.build()
|
||||
|
||||
assertFileExists(destination1, artifactFileName("main", ArtifactType.LIBRARY))
|
||||
assertFileExists(destination2, artifactFileName("main", ArtifactType.LIBRARY))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Up-to-date checks should work with different directories for different targets`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory)
|
||||
val fooDir = project.createSubDir("foo")
|
||||
val barDir = project.createSubDir("bar")
|
||||
project.buildFile.appendText("""
|
||||
apply plugin: 'konan'
|
||||
|
||||
konanArtifacts {
|
||||
library('foo')
|
||||
library('bar')
|
||||
}
|
||||
|
||||
task assertUpToDate {
|
||||
dependsOn 'compileKonanFoo'
|
||||
doLast {
|
||||
if (!konanArtifacts.foo.getByTarget('host').state.upToDate) {
|
||||
throw new AssertionError("Compilation task is not up-to-date")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
project.generateSrcFile("main.kt")
|
||||
|
||||
project.createRunner()
|
||||
.withArguments("compileKonanFoo", "-Pkonan.configuration.build.dir=${fooDir.absolutePath}")
|
||||
.build()
|
||||
project.createRunner()
|
||||
.withArguments("compileKonanBar", "-Pkonan.configuration.build.dir=${barDir.absolutePath}")
|
||||
.build()
|
||||
project.createRunner()
|
||||
.withArguments("assertUpToDate", "-Pkonan.configuration.build.dir=${fooDir.absolutePath}")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TaskTests {
|
||||
val tmpFolder = TemporaryFolder()
|
||||
@Rule get
|
||||
|
||||
val projectDirectory: File
|
||||
get() = tmpFolder.root
|
||||
|
||||
@Test
|
||||
fun `Plugin should support separate run tasks for different binaries`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory).apply {
|
||||
buildFile.appendText("""
|
||||
konanArtifacts {
|
||||
program('foo') {
|
||||
srcDir 'src/foo/kotlin'
|
||||
}
|
||||
program('bar') {
|
||||
srcDir 'src/bar/kotlin'
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
project.generateSrcFile(
|
||||
listOf("src", "foo", "kotlin"),
|
||||
"main.kt",
|
||||
"fun main(args: Array<String>) = println(\"Run Foo: \${args[0]}, \${args[1]}\")")
|
||||
project.generateSrcFile(
|
||||
listOf("src", "bar", "kotlin"),
|
||||
"main.kt",
|
||||
"fun main(args: Array<String>) = println(\"Run Bar: \${args[0]}, \${args[1]}\")")
|
||||
val resultFoo = project.createRunner()
|
||||
.withArguments("runFoo", "-PrunArgs=arg1 arg2")
|
||||
.build()
|
||||
val resultAll = project.createRunner()
|
||||
.withArguments("run", "-PrunArgs=arg1 arg2")
|
||||
.build()
|
||||
|
||||
assertTrue(resultFoo.output.contains("Run Foo: arg1, arg2"), "No Foo output for 'runFoo'")
|
||||
assertFalse(resultFoo.output.contains("Run Bar: "), "There is Bar output for 'runFoo'")
|
||||
assertTrue(resultAll.output.contains("Run Foo: arg1, arg2"), "No Foo output for 'run'")
|
||||
assertTrue(resultAll.output.contains("Run Bar: arg1, arg2"), "No Bar output for 'run'")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.gradle.plugin.test
|
||||
|
||||
import org.gradle.api.internal.project.ProjectInternal
|
||||
import org.gradle.testfixtures.ProjectBuilder
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.konan.konanArtifactsContainer
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.KonanToolingModelBuilder
|
||||
import org.jetbrains.kotlin.konan.CURRENT
|
||||
import org.jetbrains.kotlin.konan.CompilerVersion
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
open class ToolingModelTests {
|
||||
val tmpFolder = TemporaryFolder()
|
||||
@Rule get
|
||||
|
||||
val projectDirectory: File
|
||||
get() = tmpFolder.root
|
||||
|
||||
fun String.escapeBackSlashes() = KonanProject.escapeBackSlashes(this)
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `The model should be serialized without exceptions`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory).apply {
|
||||
buildFile.appendText("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
srcDir 'src/foo/kotlin'
|
||||
}
|
||||
interop('bar')
|
||||
|
||||
program('main') {
|
||||
libraries {
|
||||
artifact konanArtifacts.foo
|
||||
artifact konanArtifacts.bar
|
||||
klib 'posix'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.*
|
||||
|
||||
task testSerialization {
|
||||
doLast {
|
||||
def model = KonanToolingModelBuilder.INSTANCE.buildAll("KonanModel", project)
|
||||
file("model.bin").withObjectOutputStream {
|
||||
it.writeObject(model)
|
||||
}
|
||||
|
||||
KonanModelImpl deserializedModel
|
||||
file("model.bin").withObjectInputStream(model.getClass().getClassLoader()) { stream ->
|
||||
deserializedModel = (KonanModelImpl) stream.readObject()
|
||||
}
|
||||
|
||||
if (!deserializedModel.equals(model)) {
|
||||
throw new AssertionError("The deserialized model doesn't equal to the initial one")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
generateSrcFile("main.kt")
|
||||
generateSrcFile(Paths.get("src", "foo", "kotlin"), "foo.kt", "fun foo() = 1")
|
||||
generateSrcFile(Paths.get("src", "bar", "kotlin"), "bar.kt", "fun bar() = 1")
|
||||
generateDefFile("baz.def", "")
|
||||
generateDefFile("qux.def", "")
|
||||
}
|
||||
project.createRunner().withArguments("testSerialization").build()
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `The model should contain the same data as the Gradle tasks`() {
|
||||
val project = KonanProject.createEmpty(projectDirectory, listOf("host", "wasm32")).apply {
|
||||
val konanVersion = CompilerVersion.CURRENT.toString()
|
||||
generateSrcFile(listOf("src", "foo1"), "foo1.kt", "fun foo1() = 0")
|
||||
generateSrcFile(listOf("src", "foo1"), "foo11.kt", "fun foo11() = 0")
|
||||
generateSrcFile(listOf("src", "foo2"), "foo2.kt", "fun foo2() = 0")
|
||||
generateSrcFile(listOf("defs_bar"), "bar.def", "")
|
||||
generateSrcFile(listOf("src", "baz"), "baz.kt", "fun baz() = 0")
|
||||
generateSrcFile("main.kt")
|
||||
createSubDir("custom_repo")
|
||||
buildFile.appendText("""
|
||||
konanArtifacts {
|
||||
library('foo') {
|
||||
srcDir 'src/foo1'
|
||||
srcDir 'src/foo2'
|
||||
}
|
||||
interop('bar') {
|
||||
defFile 'defs_bar/bar.def'
|
||||
}
|
||||
library('baz') {
|
||||
srcDir 'src/baz'
|
||||
}
|
||||
|
||||
program('main') {
|
||||
libraries {
|
||||
artifact konanArtifacts.foo
|
||||
artifact konanArtifacts.bar
|
||||
klib 'baz'
|
||||
|
||||
useRepo 'custom_repo'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
konan {
|
||||
languageVersion = "1.2"
|
||||
apiVersion = "1.2"
|
||||
}
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.model.*
|
||||
import shadow.org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
|
||||
public <T> void assertEquals(
|
||||
T actual,
|
||||
T expected,
|
||||
String message = "${'$'}expected expected but ${'$'}actual found") {
|
||||
if (expected != actual) {
|
||||
throw new AssertionError(message)
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void assertContentEquals(
|
||||
Collection<T> actual,
|
||||
Collection<T> expected,
|
||||
String message = "${'$'}expected\nexpected but\n${'$'}actual\nfound") {
|
||||
if (actual.size() != expected.size() || !actual.containsAll(expected)) {
|
||||
throw new AssertionError(message)
|
||||
}
|
||||
}
|
||||
|
||||
task testModelData {
|
||||
dependsOn('compileKonanBaz')
|
||||
doLast {
|
||||
def model = KonanToolingModelBuilder.INSTANCE.buildAll("KonanModel", project)
|
||||
assertEquals(model.konanHome, file(project.getProperty('org.jetbrains.kotlin.native.home')))
|
||||
assertEquals(model.konanVersion.toString(), "$konanVersion")
|
||||
|
||||
assertEquals(model.languageVersion, "1.2")
|
||||
assertEquals(model.apiVersion, "1.2")
|
||||
|
||||
model.artifacts.each {
|
||||
|
||||
def konanArtifact = konanArtifacts[it.name]
|
||||
def target = it.targetPlatform
|
||||
def task = konanArtifact.getByTarget(target)
|
||||
|
||||
assertEquals(it.file, task.artifact)
|
||||
assertEquals(it.buildTaskName, task.name)
|
||||
|
||||
switch(it.name) {
|
||||
case 'foo':
|
||||
assertEquals(it.type, CompilerOutputKind.valueOf('LIBRARY'))
|
||||
assertContentEquals(it.srcDirs, [file('src/foo1'), file('src/foo2')])
|
||||
assertContentEquals(it.srcFiles, [
|
||||
file('src/foo1/foo1.kt'),
|
||||
file('src/foo1/foo11.kt'),
|
||||
file('src/foo2/foo2.kt')])
|
||||
assertContentEquals(it.libraries, [])
|
||||
assertContentEquals(it.searchPaths, [task.artifact.parentFile])
|
||||
break
|
||||
case 'bar':
|
||||
assertEquals(it.type, CompilerOutputKind.valueOf('LIBRARY'))
|
||||
assertContentEquals(it.srcDirs, [file('defs_bar')])
|
||||
assertContentEquals(it.srcFiles, [file('defs_bar/bar.def')])
|
||||
assertContentEquals(it.libraries, [])
|
||||
assertContentEquals(it.searchPaths, [task.artifact.parentFile])
|
||||
break
|
||||
case 'main':
|
||||
assertEquals(it.type, CompilerOutputKind.valueOf('PROGRAM'))
|
||||
assertContentEquals(it.srcDirs, [file('src/main/kotlin')])
|
||||
assertContentEquals(it.srcFiles, [file('src/main/kotlin/main.kt')])
|
||||
assertContentEquals(it.libraries, [
|
||||
konanArtifacts['foo'].getByTarget(target).artifact,
|
||||
konanArtifacts['bar'].getByTarget(target).artifact,
|
||||
konanArtifacts['baz'].getByTarget(target).artifact
|
||||
])
|
||||
assertContentEquals(it.searchPaths, [
|
||||
file('custom_repo'),
|
||||
task.artifact.parentFile,
|
||||
konanArtifacts.foo.getByTarget(target).artifact.parentFile
|
||||
])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
project.createRunner().withArguments("testModelData").build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `The model should support maven libraries`() {
|
||||
val repoDir = projectDirectory.resolve("repo").absolutePath.escapeBackSlashes()
|
||||
val dependency = KonanProject.createEmpty(projectDirectory).apply {
|
||||
buildFile.appendText("""
|
||||
group 'test'
|
||||
version '1.0'
|
||||
|
||||
konanArtifacts {
|
||||
library('foo')
|
||||
}
|
||||
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url = '$repoDir'
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
generateSrcFile("main.kt")
|
||||
}
|
||||
dependency.createRunner().withArguments("build", "publish").build()
|
||||
|
||||
val dependentDir = projectDirectory.resolve("dependent").apply {
|
||||
mkdirs()
|
||||
}
|
||||
|
||||
|
||||
val dependent = ProjectBuilder.builder().withProjectDir(dependentDir).build() as ProjectInternal
|
||||
|
||||
with(dependent) {
|
||||
pluginManager.apply(KonanPlugin::class.java)
|
||||
konanArtifactsContainer.library("bar")
|
||||
repositories.maven {
|
||||
it.setUrl(repoDir)
|
||||
}
|
||||
dependencies.apply {
|
||||
add("artifactbar", "test:foo:1.0")
|
||||
}
|
||||
}
|
||||
val model = KonanToolingModelBuilder.buildAll("konanModel", dependent)
|
||||
assertEquals(1, model.artifacts.size, "Incorrect number of artifacts.")
|
||||
val libraries = model.artifacts[0].libraries
|
||||
assertEquals(1, libraries.size, "Incorrect number of libraries.")
|
||||
val library = libraries[0].name
|
||||
assertEquals("foo.klib", library, "Incorrect library name.")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../benchmarks/shared/src'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
implementation(npm("aws-sdk", "~2.670.0"))
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir 'src/main/kotlin-js'
|
||||
kotlin.srcDir 'shared/src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
targets {
|
||||
fromPreset(presets.js, 'js') {
|
||||
nodejs()
|
||||
compilations.main.kotlinOptions {
|
||||
outputFile = "${projectDir}/server/app.js"
|
||||
moduleKind = "commonjs"
|
||||
sourceMap = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,529 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
||||
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.18",
|
||||
"negotiator": "0.6.1"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.670.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.670.0.tgz",
|
||||
"integrity": "sha512-hGRnZtp1wDUh6hZRBHO0Ki7thx/xbRlIEiTKlWes+f/0E1Nhm3KpelsBZ3L/Q6y1ragwkQd4Q720AmWEqemLyA==",
|
||||
"requires": {
|
||||
"buffer": "4.9.1",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.13",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.3.2",
|
||||
"xml2js": "0.4.19"
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
"isarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
|
||||
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.18.3",
|
||||
"content-disposition": "0.5.2",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.1.1",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.4",
|
||||
"qs": "6.5.2",
|
||||
"range-parser": "~1.2.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.16.2",
|
||||
"serve-static": "1.13.2",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": "~1.4.0",
|
||||
"type-is": "~1.6.16",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.4.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": ">= 1.4.0 < 2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
|
||||
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"jmespath": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"kotlin": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0.tgz",
|
||||
"integrity": "sha512-q+Ts9Xr72eT3QkmLjHDObPAHbsKbZ/Vn0ozqrNNr7UbtpLxa26+Hn8ILORPLz1UIUTespZyfXztXJ7AO5Xl/Gg=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.38.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
|
||||
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.22",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
|
||||
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
|
||||
"requires": {
|
||||
"mime-db": "~1.38.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
||||
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.8.0"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.6.2",
|
||||
"mime": "1.4.1",
|
||||
"ms": "2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.0",
|
||||
"statuses": "~1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.2",
|
||||
"send": "0.16.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.16",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.18"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"url": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
||||
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
|
||||
"requires": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~9.0.1"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"main": "server/app.js",
|
||||
"scripts": {
|
||||
"start": "node server/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "~2.670.0",
|
||||
"body-parser": "~1.18.3",
|
||||
"debug": "~4.1.1",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.4",
|
||||
"kotlin": "~1.4.0",
|
||||
"node-fetch": "~2.6.1"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.buildInfo
|
||||
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
data class Build(val buildNumber: String, val startTime: String, val finishTime: String, val branch: String,
|
||||
val commits: String, val failuresNumber: Int) {
|
||||
|
||||
companion object : EntityFromJsonFactory<Build> {
|
||||
override fun create(data: JsonElement): Build {
|
||||
if (data is JsonObject) {
|
||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber").replace("\"", "")
|
||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime").replace("\"", "")
|
||||
val finishTime = elementToString(data.getRequiredField("finishTime"), "finishTime").replace("\"", "")
|
||||
val branch = elementToString(data.getRequiredField("branch"), "branch").replace("\"", "")
|
||||
val commits = elementToString(data.getRequiredField("commits"), "commits")
|
||||
val failuresNumber = elementToInt(data.getRequiredField("failuresNumber"), "failuresNumber")
|
||||
return Build(buildNumber, startTime, finishTime, branch, commits, failuresNumber)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatTime(time: String, targetZone: Int = 3): String {
|
||||
val matchResult = "^\\d{8}T(\\d{2})(\\d{2})\\d{2}((\\+|-)\\d{2})".toRegex().find(time)?.groupValues
|
||||
matchResult?.let {
|
||||
val timeZone = matchResult[3].toInt()
|
||||
val timeDifference = targetZone - timeZone
|
||||
var hours = (matchResult[1].toInt() + timeDifference)
|
||||
if (hours > 23) {
|
||||
hours -= 24
|
||||
}
|
||||
return "${if (hours < 10) "0$hours" else "$hours"}:${matchResult[2]}"
|
||||
} ?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
|
||||
val date: String by lazy {
|
||||
val matchResult = "^(\\d{4})(\\d{2})(\\d{2})".toRegex().find(startTime)?.groupValues
|
||||
matchResult?.let { "${matchResult[3]}/${matchResult[2]}/${matchResult[1]}" }
|
||||
?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
val formattedStartTime: String by lazy {
|
||||
formatTime(startTime)
|
||||
}
|
||||
val formattedFinishTime: String by lazy {
|
||||
formatTime(finishTime)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.elastic
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.network.*
|
||||
|
||||
// Connector with InfluxDB.
|
||||
class ElasticSearchConnector(private val connector: NetworkConnector,
|
||||
private val user: String? = null, private val password: String? = null) {
|
||||
// Execute ElasticSearch request.
|
||||
fun request(method: RequestMethod, path: String, acceptJsonContentType: Boolean = true, body: String? = null) =
|
||||
connector.sendRequest(method, path, user, password, acceptJsonContentType, body)
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.elastic
|
||||
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
import org.jetbrains.network.*
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
|
||||
data class Commit(val revision: String, val developer: String) : JsonSerializable {
|
||||
override fun toString() = "$revision by $developer"
|
||||
|
||||
override fun serializeFields() = """
|
||||
"revision": "$revision",
|
||||
"developer": "$developer"
|
||||
"""
|
||||
|
||||
companion object : EntityFromJsonFactory<Commit> {
|
||||
fun parse(description: String) = if (description != "...") {
|
||||
description.split(" by ").let {
|
||||
val (currentRevision, currentDeveloper) = it
|
||||
Commit(currentRevision, currentDeveloper)
|
||||
}
|
||||
} else {
|
||||
Commit("unknown", "unknown")
|
||||
}
|
||||
|
||||
override fun create(data: JsonElement): Commit {
|
||||
if (data is JsonObject) {
|
||||
val revision = elementToString(data.getRequiredField("revision"), "revision")
|
||||
val developer = elementToString(data.getRequiredField("developer"), "developer")
|
||||
return Commit(revision, developer)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of commits.
|
||||
class CommitsList : ConvertedFromJson, JsonSerializable {
|
||||
|
||||
val commits: List<Commit>
|
||||
|
||||
constructor(data: JsonElement) {
|
||||
if (data !is JsonObject) {
|
||||
error("Commits description is expected to be a JSON object!")
|
||||
}
|
||||
val changesElement = data.getOptionalField("change")
|
||||
commits = changesElement?.let {
|
||||
if (changesElement !is JsonArray) {
|
||||
error("Change field is expected to be an array. Please, check source.")
|
||||
}
|
||||
changesElement.jsonArray.map {
|
||||
with(it as JsonObject) {
|
||||
Commit(elementToString(getRequiredField("version"), "version"),
|
||||
elementToString(getRequiredField("username"), "username")
|
||||
)
|
||||
}
|
||||
}
|
||||
} ?: listOf<Commit>()
|
||||
}
|
||||
|
||||
constructor(_commits: List<Commit>) {
|
||||
commits = _commits
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
commits.toString()
|
||||
|
||||
companion object {
|
||||
fun parse(description: String) = CommitsList(description.split(";").filter { it.isNotEmpty() }.map {
|
||||
Commit.parse(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun serializeFields() = """
|
||||
"commits": ${arrayToJson(commits)}
|
||||
"""
|
||||
}
|
||||
|
||||
data class BuildInfo(val buildNumber: String, val startTime: String, val endTime: String, val commitsList: CommitsList,
|
||||
val branch: String,
|
||||
val agentInfo: String /* Important agent information often used in requests.*/) : JsonSerializable {
|
||||
override fun serializeFields() = """
|
||||
"buildNumber": "$buildNumber",
|
||||
"startTime": "$startTime",
|
||||
"endTime": "$endTime",
|
||||
${commitsList.serializeFields()},
|
||||
"branch": "$branch",
|
||||
"agentInfo": "$agentInfo"
|
||||
"""
|
||||
|
||||
companion object : EntityFromJsonFactory<BuildInfo> {
|
||||
override fun create(data: JsonElement): BuildInfo {
|
||||
if (data is JsonObject) {
|
||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber")
|
||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime")
|
||||
val endTime = elementToString(data.getRequiredField("endTime"), "endTime")
|
||||
val branch = elementToString(data.getRequiredField("branch"), "branch")
|
||||
val commitsList = data.getRequiredField("commits")
|
||||
val commits = if (commitsList is JsonArray) {
|
||||
commitsList.jsonArray.map { Commit.create(it as JsonObject) }
|
||||
} else {
|
||||
error("benchmarksSets field is expected to be an array. Please, check origin files.")
|
||||
}
|
||||
val agentInfoElement = data.getOptionalField("agentInfo")
|
||||
val agentInfo = agentInfoElement?.let {
|
||||
elementToString(agentInfoElement, "agentInfo")
|
||||
} ?: ""
|
||||
return BuildInfo(buildNumber, startTime, endTime, CommitsList(commits), branch, agentInfo)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ElasticSearchType {
|
||||
TEXT, KEYWORD, DATE, LONG, DOUBLE, BOOLEAN, OBJECT, NESTED
|
||||
}
|
||||
|
||||
abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) {
|
||||
// Insert data.
|
||||
fun insert(data: JsonSerializable): Promise<String> {
|
||||
val description = data.toJson()
|
||||
val writePath = "$indexName/_doc/"
|
||||
return connector.request(RequestMethod.POST, writePath, body = description)
|
||||
}
|
||||
|
||||
// Delete data.
|
||||
fun delete(data: String): Promise<String> {
|
||||
val writePath = "$indexName/_delete_by_query"
|
||||
return connector.request(RequestMethod.POST, writePath, body = data)
|
||||
}
|
||||
|
||||
// Make search request.
|
||||
fun search(requestJson: String, filterPathes: List<String> = emptyList()): Promise<String> {
|
||||
val path = "$indexName/_search?pretty${if (filterPathes.isNotEmpty())
|
||||
"&filter_path=" + filterPathes.joinToString(",") else ""}"
|
||||
return connector.request(RequestMethod.POST, path, body = requestJson)
|
||||
}
|
||||
|
||||
abstract val mapping: Map<String, ElasticSearchType>
|
||||
|
||||
val mappingDescription: String
|
||||
get() = """
|
||||
{
|
||||
"mappings": {
|
||||
"properties": {
|
||||
${mapping.map { (property, type) ->
|
||||
"\"${property}\": { \"type\": \"${type.name.toLowerCase()}\"${if (type == ElasticSearchType.DATE) "," +
|
||||
"\"format\": \"basic_date_time_no_millis\"" else ""} }"
|
||||
}.joinToString()}}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
fun createMapping() =
|
||||
connector.request(RequestMethod.PUT, indexName, body = mappingDescription)
|
||||
}
|
||||
|
||||
class BenchmarksIndex(name: String, connector: ElasticSearchConnector) : ElasticSearchIndex(name, connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"benchmarks" to ElasticSearchType.NESTED,
|
||||
"env" to ElasticSearchType.NESTED,
|
||||
"kotlin" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
class GoldenResultsIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("golden", connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"benchmarks" to ElasticSearchType.NESTED,
|
||||
"env" to ElasticSearchType.NESTED,
|
||||
"kotlin" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
class BuildInfoIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("builds", connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"startTime" to ElasticSearchType.DATE,
|
||||
"endTime" to ElasticSearchType.DATE,
|
||||
"commits" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
// Processed benchmark result with calculated mean, variance and normalized reult.
|
||||
class NormalizedMeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
|
||||
runtimeInUs: Double, repeat: Int, warmup: Int, variance: Double, val normalizedScore: Double) :
|
||||
MeanVarianceBenchmark(name, status, score, metric, runtimeInUs, repeat, warmup, variance) {
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
${super.serializeFields()},
|
||||
"normalizedScore": $normalizedScore
|
||||
"""
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
|
||||
// Now implemenation for network connection only for Node.js. TODO - multiplatform.
|
||||
external fun require(module: String): dynamic
|
||||
|
||||
enum class RequestMethod {
|
||||
POST, GET, PUT
|
||||
}
|
||||
|
||||
// Abstract class for working with network.
|
||||
abstract class NetworkConnector {
|
||||
fun getAuth(user: String, password: String): String {
|
||||
val buffer = js("Buffer").from(user + ":" + password)
|
||||
val based64String = buffer.toString("base64")
|
||||
return "Basic " + based64String
|
||||
}
|
||||
|
||||
protected abstract fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String? = null,
|
||||
password: String? = null, acceptJsonContentType: Boolean = true,
|
||||
body: String? = null,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T>
|
||||
|
||||
open fun sendRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String> =
|
||||
sendBaseRequest<String>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||
error("Error during getting response from $url\n$response")
|
||||
}
|
||||
|
||||
open fun sendOptionalRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String?> =
|
||||
sendBaseRequest<String?>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||
println("Error during getting response from $url\n$response")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
|
||||
// Network connector to work with basic url requests.
|
||||
class UrlNetworkConnector(private val host: String, private val port: Int? = null) : NetworkConnector() {
|
||||
|
||||
private val url = "$host${port?.let { ":$port" } ?: ""}"
|
||||
|
||||
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||
acceptJsonContentType: Boolean, body: String?,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||
val fullUrl = "$url/$path"
|
||||
val request = require("node-fetch")
|
||||
val headers = mutableListOf<Pair<String, String>>()
|
||||
if (user != null && password != null) {
|
||||
headers.add("Authorization" to getAuth(user, password))
|
||||
}
|
||||
if (acceptJsonContentType) {
|
||||
headers.add("Accept" to "application/json")
|
||||
headers.add("Content-Type" to "application/json")
|
||||
}
|
||||
|
||||
return request(fullUrl,
|
||||
json(
|
||||
"method" to method.toString(),
|
||||
"headers" to json(*(headers.toTypedArray())),
|
||||
"body" to body
|
||||
)
|
||||
).then { response ->
|
||||
if (!response.ok) {
|
||||
println(JSON.stringify(response))
|
||||
errorHandler(fullUrl, response)
|
||||
} else {
|
||||
response.text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.browser.*
|
||||
import kotlin.js.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
error("Reading from local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
error("Writing to local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
this.asDynamic().toFixed(decimalNumber)
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) {
|
||||
if (!value) error(lazyMessage)
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
error("Unsupported")
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.database
|
||||
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.utils.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
fun <T> Iterable<T>.isEmpty() = count() == 0
|
||||
fun <T> Iterable<T>.isNotEmpty() = !isEmpty()
|
||||
|
||||
inline fun <T: Any> T?.str(block: (T) -> String): String =
|
||||
if (this != null) block(this)
|
||||
else ""
|
||||
|
||||
// Dispatcher to create and control benchmarks indexes separated by some feature.
|
||||
// Feature can be choosen as often used as filtering entity in case there is no need in separate indexes.
|
||||
// Default behaviour of dispatcher is working with one index (case when separating isn't needed).
|
||||
class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature: String,
|
||||
featureValues: Iterable<String> = emptyList()) {
|
||||
// Becnhmarks indexes to work with in case of existing feature values.
|
||||
private val benchmarksIndexes =
|
||||
if (featureValues.isNotEmpty())
|
||||
featureValues.map { it to BenchmarksIndex("benchmarks_${it.replace(" ", "_").toLowerCase()}", connector) }
|
||||
.toMap()
|
||||
else emptyMap()
|
||||
|
||||
// Single benchmark index.
|
||||
private val benchmarksSingleInstance =
|
||||
if (featureValues.isEmpty()) BenchmarksIndex("benchmarks", connector) else null
|
||||
|
||||
// Get right index in ES.
|
||||
private fun getIndex(featureValue: String = "") =
|
||||
benchmarksSingleInstance ?: benchmarksIndexes[featureValue]
|
||||
?: error("Used wrong feature value $featureValue. Indexes are separated using next values: ${benchmarksIndexes.keys}")
|
||||
|
||||
// Used filter to get data with needed feature value.
|
||||
var featureFilter: ((String) -> String)? = null
|
||||
|
||||
// Get benchmark reports corresponding to needed build number.
|
||||
fun getBenchmarksReports(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"size": 1000,
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "buildNumber": "$buildNumber" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.let { results ->
|
||||
results.map {
|
||||
val element = it as JsonObject
|
||||
element.getObject("_source").toString()
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// Get benchmarkes names corresponding to needed build number.
|
||||
fun getBenchmarksList(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||
return getBenchmarksReports(buildNumber, featureValue).then { reports ->
|
||||
reports.map {
|
||||
val dbResponse = JsonTreeParser.parse(it).jsonObject
|
||||
parseBenchmarksArray(dbResponse.getArray("benchmarks"))
|
||||
.map { it.name }
|
||||
}.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete benchmarks from database.
|
||||
fun deleteBenchmarks(featureValue: String, buildNumber: String? = null): Promise<String> {
|
||||
// Delete all or for choosen build number.
|
||||
val matchQuery = buildNumber?.let {
|
||||
""""match": { "buildNumber": "$it" }"""
|
||||
} ?: """"match_all": {}"""
|
||||
|
||||
val queryDescription = """
|
||||
{
|
||||
"query": {
|
||||
$matchQuery
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return getIndex(featureValue).delete(queryDescription)
|
||||
}
|
||||
|
||||
// Get benchmarks values of needed metric for choosen build number.
|
||||
fun getSamples(metricName: String, featureValue: String = "", samples: List<String>, buildsCountToShow: Int,
|
||||
buildNumbers: Iterable<String>? = null,
|
||||
normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": ["buildNumber"],
|
||||
"size": ${samples.size * buildsCountToShow},
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
{ "terms" : { "buildNumber" : [${builds.map { "\"$it\"" }.joinToString()}] } },""" }
|
||||
}
|
||||
${featureFilter.str { "${it(featureValue)}," } }
|
||||
{"nested" : {
|
||||
"path" : "benchmarks",
|
||||
"query" : {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "benchmarks.metric": "$metricName" } },
|
||||
{ "terms": { "benchmarks.name": [${samples.map { "\"${it.toLowerCase()}\"" }.joinToString()}] }}
|
||||
]
|
||||
}
|
||||
}, "inner_hits": {
|
||||
"size": ${samples.size},
|
||||
"_source": ["benchmarks.name",
|
||||
"benchmarks.${if (normalize) "normalizedScore" else "score"}"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source", "hits.hits.inner_hits"))
|
||||
.then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val results = dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
// Get indexes for provided samples.
|
||||
val indexesMap = samples.mapIndexed { index, it -> it to index }.toMap()
|
||||
val valuesMap = buildNumbers?.map {
|
||||
it to arrayOfNulls<Double?>(samples.size)
|
||||
}?.toMap()?.toMutableMap() ?: mutableMapOf<String, Array<Double?>>()
|
||||
// Parse and save values in requested order.
|
||||
results.forEach {
|
||||
val element = it as JsonObject
|
||||
val build = element.getObject("_source").getPrimitive("buildNumber").content
|
||||
buildNumbers?.let { valuesMap.getOrPut(build) { arrayOfNulls<Double?>(samples.size) } }
|
||||
element
|
||||
.getObject("inner_hits")
|
||||
.getObject("benchmarks")
|
||||
.getObject("hits")
|
||||
.getArray("hits").forEach {
|
||||
val source = (it as JsonObject).getObject("_source")
|
||||
valuesMap[build]!![indexesMap[source.getPrimitive("name").content]!!] =
|
||||
source.getPrimitive(if (normalize) "normalizedScore" else "score").double
|
||||
}
|
||||
|
||||
}
|
||||
valuesMap.toList()
|
||||
}
|
||||
}
|
||||
|
||||
fun insert(data: JsonSerializable, featureValue: String = "") =
|
||||
getIndex(featureValue).insert(data)
|
||||
|
||||
fun delete(data: String, featureValue: String = "") =
|
||||
getIndex(featureValue).delete(data)
|
||||
|
||||
// Get failures number happned during build.
|
||||
fun getFailuresNumber(featureValue: String = "", buildNumbers: Iterable<String>? = null): Promise<Map<String, Int>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": false,
|
||||
${featureFilter.str {
|
||||
"""
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [ ${it(featureValue)} ]
|
||||
}
|
||||
}, """
|
||||
} }
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
"aggs" : {
|
||||
"builds": {
|
||||
"filters" : {
|
||||
"filters": {
|
||||
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||
.joinToString(",\n")}
|
||||
}
|
||||
},"""
|
||||
} }
|
||||
"aggs" : {
|
||||
"metric_build" : {
|
||||
"nested" : {
|
||||
"path" : "benchmarks"
|
||||
},
|
||||
"aggs" : {
|
||||
"metric_samples": {
|
||||
"filters" : {
|
||||
"filters": { "samples": { "match": { "benchmarks.status": "FAILED" } } }
|
||||
},
|
||||
"aggs" : {
|
||||
"failed_count": {
|
||||
"value_count": {
|
||||
"field" : "benchmarks.score"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${buildNumbers.str {
|
||||
""" }
|
||||
}"""
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||
buildNumbers?.let {
|
||||
// Get failed number for each provided build.
|
||||
val buckets = aggregations
|
||||
.getObjectOrNull("builds")
|
||||
?.getObjectOrNull("buckets")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
buildNumbers.map {
|
||||
it to buckets
|
||||
.getObject(it)
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("failed_count")
|
||||
.getPrimitive("value")
|
||||
.int
|
||||
}.toMap()
|
||||
} ?: listOf("golden" to aggregations
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("failed_count")
|
||||
.getPrimitive("value")
|
||||
.int
|
||||
).toMap()
|
||||
}
|
||||
}
|
||||
|
||||
// Get geometric mean for benchmarks values of needed metric.
|
||||
fun getGeometricMean(metricName: String, featureValue: String = "",
|
||||
buildNumbers: Iterable<String>? = null, normalize: Boolean = false,
|
||||
excludeNames: List<String> = emptyList()): Promise<List<Pair<String, List<Double?>>>> {
|
||||
// Filter only with metric or also with names.
|
||||
val filterBenchmarks = if (excludeNames.isEmpty())
|
||||
"""
|
||||
"match": { "benchmarks.metric": "$metricName" }
|
||||
"""
|
||||
else """
|
||||
"bool": {
|
||||
"must": { "match": { "benchmarks.metric": "$metricName" } },
|
||||
"must_not": { "terms" : { "benchmarks.name" : [${excludeNames.map { "\"$it\"" }.joinToString()}] } }
|
||||
}
|
||||
""".trimIndent()
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": false,
|
||||
${featureFilter.str {
|
||||
"""
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [ ${it(featureValue)} ]
|
||||
}
|
||||
}, """
|
||||
} }
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
"aggs" : {
|
||||
"builds": {
|
||||
"filters" : {
|
||||
"filters": {
|
||||
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||
.joinToString(",\n")}
|
||||
}
|
||||
},"""
|
||||
} }
|
||||
"aggs" : {
|
||||
"metric_build" : {
|
||||
"nested" : {
|
||||
"path" : "benchmarks"
|
||||
},
|
||||
"aggs" : {
|
||||
"metric_samples": {
|
||||
"filters" : {
|
||||
"filters": { "samples": { $filterBenchmarks } }
|
||||
},
|
||||
"aggs" : {
|
||||
"sum_log_x": {
|
||||
"sum": {
|
||||
"field" : "benchmarks.${if (normalize) "normalizedScore" else "score"}",
|
||||
"script" : {
|
||||
"source": "if (_value == 0) { 0.0 } else { Math.log(_value) }"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geom_mean": {
|
||||
"bucket_script": {
|
||||
"buckets_path": {
|
||||
"sum_log_x": "sum_log_x",
|
||||
"x_cnt": "_count"
|
||||
},
|
||||
"script": "Math.exp(params.sum_log_x/params.x_cnt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${buildNumbers.str {
|
||||
""" }
|
||||
}"""
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||
buildNumbers?.let {
|
||||
val buckets = aggregations
|
||||
.getObjectOrNull("builds")
|
||||
?.getObjectOrNull("buckets")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
buildNumbers.map {
|
||||
it to listOf(buckets
|
||||
.getObject(it)
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
}
|
||||
} ?: listOf("golden" to listOf(aggregations
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.database
|
||||
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.utils.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
// Delete build information from ES index.
|
||||
internal fun deleteBuildInfo(agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||
buildNumber: String? = null): Promise<String> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "agentInfo": "$agentInfo" } }
|
||||
${buildNumber?.let {
|
||||
""",
|
||||
{"match": { "buildNumber": "$it" }}
|
||||
"""
|
||||
} ?: ""}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return buildInfoIndex.delete(queryDescription)
|
||||
}
|
||||
|
||||
// Get infromation about builds details from database.
|
||||
internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||
buildsCountToShow: Int, beforeDate: String?, afterDate: String?,
|
||||
onlyNumbers: Boolean = false): Promise<JsonArray> {
|
||||
val queryDescription = """
|
||||
{ "size": $buildsCountToShow,
|
||||
${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""}
|
||||
"sort": {"startTime": "desc" },
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "agentInfo": "$agentInfo" } }
|
||||
${type?.let {
|
||||
""",
|
||||
{ "regexp": { "buildNumber": { "value": "${if (it == "release")
|
||||
".*eap.*|.*release.*|.*rc.*" else ".*dev.*"}" } }
|
||||
}
|
||||
"""
|
||||
} ?: ""}
|
||||
${beforeDate?.let {
|
||||
""",
|
||||
{ "range": { "startTime": { "lt": "$it" } } }
|
||||
"""
|
||||
} ?: ""}
|
||||
${afterDate?.let {
|
||||
""",
|
||||
{ "range": { "startTime": { "gt": "$it" } } }
|
||||
"""
|
||||
} ?: ""}
|
||||
${branch?.let {
|
||||
""",
|
||||
{"match": { "branch": "$it" }}
|
||||
"""
|
||||
} ?: ""}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits") ?: error("Wrong response:\n$responseString")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current build already exists.
|
||||
suspend fun buildExists(buildInfo: BuildInfo, buildInfoIndex: ElasticSearchIndex): Boolean {
|
||||
val queryDescription = """
|
||||
{ "size": 1,
|
||||
"_source": ["buildNumber"],
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "buildNumber": "${buildInfo.buildNumber}" } },
|
||||
{ "match": { "agentInfo": "${buildInfo.agentInfo}" } },
|
||||
{ "match": { "branch": "${buildInfo.branch}" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
return buildInfoIndex.search(queryDescription, listOf("hits.total.value")).then { responseString ->
|
||||
val response = JsonTreeParser.parse(responseString).jsonObject
|
||||
val value = response.getObjectOrNull("hits")?.getObjectOrNull("total")?.getPrimitiveOrNull("value")?.content
|
||||
?: error("Error response from ElasticSearch:\n$responseString")
|
||||
value.toInt() > 0
|
||||
}.await()
|
||||
}
|
||||
|
||||
// Get builds numbers corresponding to machine and branch.
|
||||
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null, afterDate: String? = null) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate, true)
|
||||
.then { responseArray ->
|
||||
responseArray.map { (it as JsonObject).getObject("_source").getPrimitive("buildNumber").content }
|
||||
}
|
||||
|
||||
// Get full builds information corresponding to machine and branch.
|
||||
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null,
|
||||
afterDate: String? = null) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate).then { responseArray ->
|
||||
responseArray.map { BuildInfo.create((it as JsonObject).getObject("_source")) }
|
||||
}
|
||||
|
||||
// Get golden results from database.
|
||||
fun getGoldenResults(goldenResultsIndex: GoldenResultsIndex): Promise<Map<String, List<BenchmarkResult>>> {
|
||||
return goldenResultsIndex.search("", listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.map {
|
||||
val reportDescription = (it as JsonObject).getObject("_source")
|
||||
BenchmarksReport.create(reportDescription).benchmarks
|
||||
}?.reduce { acc, it -> acc + it } ?: error("Wrong format of response:\n $responseString")
|
||||
}
|
||||
}
|
||||
|
||||
// Get distinct values for needed field from database.
|
||||
fun distinctValues(field: String, index: ElasticSearchIndex): Promise<List<String>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"aggs": {
|
||||
"unique": {"terms": {"field": "$field", "size": 1000}}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return index.search(queryDescription, listOf("aggregations.unique.buckets")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("aggregations")?.getObjectOrNull("unique")?.getArrayOrNull("buckets")
|
||||
?.map { (it as JsonObject).getPrimitiveOrNull("key")?.content }?.filterNotNull()
|
||||
?: error("Wrong response:\n$responseString")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
external fun require(module: String): dynamic
|
||||
|
||||
external val process: dynamic
|
||||
external val __dirname: dynamic
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
println("Server Starting!")
|
||||
|
||||
val express = require("express")
|
||||
val app = express()
|
||||
val path = require("path")
|
||||
val bodyParser = require("body-parser")
|
||||
val http = require("http")
|
||||
// Get port from environment and store in Express.
|
||||
val port = normalizePort(process.env.PORT)
|
||||
app.use(bodyParser.json())
|
||||
app.set("port", port)
|
||||
|
||||
// View engine setup.
|
||||
app.set("views", path.join(__dirname, "../ui"))
|
||||
app.set("view engine", "ejs")
|
||||
app.use(express.static("ui"))
|
||||
|
||||
val server = http.createServer(app)
|
||||
app.listen(port, {
|
||||
println("App listening on port " + port + "!")
|
||||
})
|
||||
|
||||
app.use("/", router())
|
||||
}
|
||||
|
||||
fun normalizePort(port: Int) =
|
||||
if (port >= 0) port else 3000
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
@file:OptIn(ExperimentalTime::class)
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.time.*
|
||||
|
||||
// Response saved in cache.
|
||||
data class CachedResponse(val cachedResult: Any, val time: TimeMark)
|
||||
|
||||
// Dispatcher for work with cachable responses.
|
||||
object CachableResponseDispatcher {
|
||||
// Storage of cached responses.
|
||||
private val cachedResponses = mutableMapOf<String, CachedResponse>()
|
||||
|
||||
private val cacheMaxSize = 200
|
||||
|
||||
// Get response. If response isn't cached, use provided action to get response.
|
||||
fun getResponse(request: dynamic, response: dynamic,
|
||||
action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) {
|
||||
cachedResponses[request.url]?.let {
|
||||
// Update cache value if needed. Update only if last result was get later than 2 minutes.
|
||||
if (it.time.elapsedNow().inMinutes > 2.0) {
|
||||
println("Cache update for ${request.url}...")
|
||||
action({ result: Any ->
|
||||
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||
}, { println("Cache update for ${request.url} failed!") })
|
||||
}
|
||||
response.json(it.cachedResult)
|
||||
} ?: run {
|
||||
action({ result: Any ->
|
||||
if (cachedResponses.size >= cacheMaxSize) {
|
||||
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||
}
|
||||
response.json(result)
|
||||
}, { response.sendStatus(400) })
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(): Unit {
|
||||
cachedResponses.clear()
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
import kotlin.js.Date
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
// Placeholder as analog for indexable type in TS.
|
||||
external interface `T$0` {
|
||||
@nativeGetter
|
||||
operator fun get(key: String): String?
|
||||
|
||||
@nativeSetter
|
||||
operator fun set(key: String, value: String)
|
||||
}
|
||||
|
||||
@JsModule("aws-sdk")
|
||||
@JsNonModule
|
||||
external object AWSInstance {
|
||||
|
||||
// Replace dynamic with some real type
|
||||
class Endpoint(domain: String)
|
||||
open class HttpRequest(endpoint: Endpoint, region: String) {
|
||||
open fun pathname(): String
|
||||
open var search: String
|
||||
open var body: String?
|
||||
open var endpoint: Endpoint
|
||||
open var headers: `T$0`
|
||||
open var method: String
|
||||
open var path: String
|
||||
}
|
||||
|
||||
class HttpClient() {
|
||||
val handleRequest: dynamic
|
||||
}
|
||||
|
||||
interface Credentials
|
||||
|
||||
class SharedIniFileCredentials(options: Map<String, String>): Credentials {
|
||||
val accessKeyId: String
|
||||
}
|
||||
|
||||
class EnvironmentCredentials(envPrefix: String): Credentials {
|
||||
val accessKeyId: String
|
||||
}
|
||||
|
||||
class Signers() {
|
||||
class V4(request: HttpRequest, subsystem: String) {
|
||||
fun addAuthorization(credentials: Credentials, date: Date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network connector to work with AWS resources.
|
||||
class AWSNetworkConnector : NetworkConnector() {
|
||||
val AWSDomain = "vpc-kotlin-perf-service-5e6ldakkdv526ii5hbclzcmpny.eu-west-1.es.amazonaws.com"
|
||||
val AWSRegion = "eu-west-1"
|
||||
|
||||
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||
acceptJsonContentType: Boolean, body: String?,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||
val useEnvironmentCredentials = true // For easy test on localhost change to false.
|
||||
val AWSEndpoint = AWSInstance.Endpoint(AWSDomain)
|
||||
var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion)
|
||||
request.method = method.toString()
|
||||
request.path += path
|
||||
request.body = body
|
||||
|
||||
request.headers["host"] = this.AWSDomain
|
||||
|
||||
if (acceptJsonContentType) {
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
request.headers["Content-Length"] = js("Buffer").byteLength(request.body)
|
||||
}
|
||||
|
||||
val credentials = if (useEnvironmentCredentials) AWSInstance.EnvironmentCredentials("AWS")
|
||||
else AWSInstance.SharedIniFileCredentials(mapOf<String, String>())
|
||||
val signer = AWSInstance.Signers.V4(request, "es")
|
||||
signer.addAuthorization(credentials, Date())
|
||||
|
||||
val client = AWSInstance.HttpClient()
|
||||
return Promise { resolve, reject ->
|
||||
client.handleRequest(request, null, { response ->
|
||||
var responseBody = ""
|
||||
response.on("data") { chunk ->
|
||||
responseBody += chunk
|
||||
chunk
|
||||
}
|
||||
response.on("end") { _ ->
|
||||
val dbResponse = JsonTreeParser.parse(responseBody).jsonObject
|
||||
// Response can fail and return 400 error for ES.
|
||||
if (dbResponse.getPrimitiveOrNull("status")?.let { it.content != "200" } ?: false) {
|
||||
println(dbResponse)
|
||||
val errorMessage = dbResponse.getObject("error").toString()
|
||||
reject(Throwable(errorMessage))
|
||||
}
|
||||
resolve(responseBody as T)
|
||||
}
|
||||
}, { error ->
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.js.json
|
||||
import kotlin.js.Date
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.database.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.network.*
|
||||
import org.jetbrains.buildInfo.Build
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.utils.*
|
||||
|
||||
// TODO - create DSL for ES requests?
|
||||
|
||||
const val teamCityUrl = "https://buildserver.labs.intellij.net/app/rest"
|
||||
const val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
||||
|
||||
operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
|
||||
|
||||
fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey)
|
||||
|
||||
external fun decodeURIComponent(url: String): String
|
||||
|
||||
// Convert saved old report to expected new format.
|
||||
internal fun convertToNewFormat(data: JsonObject): List<Any> {
|
||||
val env = Environment.create(data.getRequiredField("env"))
|
||||
val benchmarksObj = data.getRequiredField("benchmarks")
|
||||
val compilerDescription = data.getRequiredField("kotlin")
|
||||
val compiler = Compiler.create(compilerDescription)
|
||||
val backend = (compilerDescription as JsonObject).getRequiredField("backend")
|
||||
val flagsArray = (backend as JsonObject).getOptionalField("flags")
|
||||
var flags: List<String> = emptyList()
|
||||
if (flagsArray != null && flagsArray is JsonArray) {
|
||||
flags = flagsArray.jsonArray.map { (it as JsonLiteral).unquoted() }
|
||||
}
|
||||
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
||||
|
||||
return listOf(env, compiler, benchmarksList, flags)
|
||||
}
|
||||
|
||||
// Convert data results to expected format.
|
||||
internal fun convert(json: String, buildNumber: String, target: String): List<BenchmarksReport> {
|
||||
val data = JsonTreeParser.parse(json)
|
||||
val reports = if (data is JsonArray) {
|
||||
data.map { convertToNewFormat(it as JsonObject) }
|
||||
} else listOf(convertToNewFormat(data as JsonObject))
|
||||
|
||||
// Restored flags for old reports.
|
||||
val knownFlags = mapOf(
|
||||
"Cinterop" to listOf("-opt"),
|
||||
"FrameworkBenchmarksAnalyzer" to listOf("-g"),
|
||||
"HelloWorld" to if (target == "Mac OS X")
|
||||
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||
else listOf("-g"),
|
||||
"Numerical" to listOf("-opt"),
|
||||
"ObjCInterop" to listOf("-opt"),
|
||||
"Ring" to listOf("-opt"),
|
||||
"Startup" to listOf("-opt"),
|
||||
"swiftInterop" to listOf("-opt"),
|
||||
"Videoplayer" to if (target == "Mac OS X")
|
||||
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||
else listOf("-g")
|
||||
)
|
||||
|
||||
return reports.map { elements ->
|
||||
val benchmarks = (elements[2] as List<BenchmarkResult>).groupBy { it.name.substringBefore('.').substringBefore(':') }
|
||||
val parsedFlags = elements[3] as List<String>
|
||||
benchmarks.map { (setName, results) ->
|
||||
val flags = if (parsedFlags.isNotEmpty() && parsedFlags[0] == "-opt") knownFlags[setName]!! else parsedFlags
|
||||
val savedCompiler = elements[1] as Compiler
|
||||
val compiler = Compiler(Compiler.Backend(savedCompiler.backend.type, savedCompiler.backend.version, flags),
|
||||
savedCompiler.kotlinVersion)
|
||||
val newReport = BenchmarksReport(elements[0] as Environment, results, compiler)
|
||||
newReport.buildNumber = buildNumber
|
||||
newReport
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
// Golden result value used to get normalized results.
|
||||
data class GoldenResult(val benchmarkName: String, val metric: String, val value: Double)
|
||||
data class GoldenResultsInfo(val goldenResults: Array<GoldenResult>)
|
||||
|
||||
// Convert information about golden results to benchmarks report format.
|
||||
fun GoldenResultsInfo.toBenchmarksReport(): BenchmarksReport {
|
||||
val benchmarksSamples = goldenResults.map {
|
||||
BenchmarkResult(it.benchmarkName, BenchmarkResult.Status.PASSED,
|
||||
it.value, BenchmarkResult.metricFromString(it.metric)!!, it.value, 1, 0)
|
||||
}
|
||||
val compiler = Compiler(Compiler.Backend(Compiler.BackendType.NATIVE, "golden", emptyList()), "golden")
|
||||
val environment = Environment(Environment.Machine("golden", "golden"), Environment.JDKInstance("golden", "golden"))
|
||||
return BenchmarksReport(environment,
|
||||
benchmarksSamples, compiler)
|
||||
}
|
||||
|
||||
// Build information provided from request.
|
||||
data class TCBuildInfo(val buildNumber: String, val branch: String, val startTime: String,
|
||||
val finishTime: String)
|
||||
|
||||
data class BuildRegister(val buildId: String, val teamCityUser: String, val teamCityPassword: String,
|
||||
val bundleSize: String?, val fileWithResult: String) {
|
||||
companion object {
|
||||
fun create(json: String): BuildRegister {
|
||||
val requestDetails = JSON.parse<BuildRegister>(json)
|
||||
// Parse method doesn't create real instance with all methods. So create it by hands.
|
||||
return BuildRegister(requestDetails.buildId, requestDetails.teamCityUser, requestDetails.teamCityPassword,
|
||||
requestDetails.bundleSize, requestDetails.fileWithResult)
|
||||
}
|
||||
}
|
||||
|
||||
private val teamCityBuildUrl: String by lazy { "builds/id:$buildId" }
|
||||
|
||||
val changesListUrl: String by lazy {
|
||||
"changes/?locator=build:id:$buildId"
|
||||
}
|
||||
|
||||
val teamCityArtifactsUrl: String by lazy { "builds/id:$buildId/artifacts/content/$fileWithResult" }
|
||||
|
||||
fun sendTeamCityRequest(url: String, json: Boolean = false) =
|
||||
UrlNetworkConnector(teamCityUrl).sendRequest(RequestMethod.GET, url, teamCityUser, teamCityPassword, json)
|
||||
|
||||
fun getBranchName(project: String): Promise<String> {
|
||||
val url = "builds?locator=id:$buildId&fields=build(revisions(revision(vcsBranchName,vcs-root-instance)))"
|
||||
var branch: String? = null
|
||||
return sendTeamCityRequest(url, true).then { response ->
|
||||
val data = JsonTreeParser.parse(response).jsonObject
|
||||
data.getArray("build").forEach {
|
||||
(it as JsonObject).getObject("revisions").getArray("revision").forEach {
|
||||
val currentBranch = (it as JsonObject).getPrimitive("vcsBranchName").content.removePrefix("refs/heads/")
|
||||
val currentProject = (it as JsonObject).getObject("vcs-root-instance").getPrimitive("name").content
|
||||
if (project == currentProject) {
|
||||
branch = currentBranch
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
branch ?: error("No project $project can be found in build $buildId")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun format(timeValue: Int): String =
|
||||
if (timeValue < 10) "0$timeValue" else "$timeValue"
|
||||
|
||||
fun getBuildInformation(): Promise<TCBuildInfo> {
|
||||
return Promise.all(arrayOf(sendTeamCityRequest("$teamCityBuildUrl/number"),
|
||||
getBranchName("Kotlin Native"),
|
||||
sendTeamCityRequest("$teamCityBuildUrl/startDate"))).then { results ->
|
||||
val (buildNumber, branch, startTime) = results
|
||||
val currentTime = Date()
|
||||
val timeZone = currentTime.getTimezoneOffset() / -60 // Convert to hours.
|
||||
// Get finish time as current time, because buid on TeamCity isn't finished.
|
||||
val finishTime = "${format(currentTime.getUTCFullYear())}" +
|
||||
"${format(currentTime.getUTCMonth() + 1)}" +
|
||||
"${format(currentTime.getUTCDate())}" +
|
||||
"T${format(currentTime.getUTCHours())}" +
|
||||
"${format(currentTime.getUTCMinutes())}" +
|
||||
"${format(currentTime.getUTCSeconds())}" +
|
||||
"${if (timeZone > 0) "+" else "-"}${format(timeZone)}${format(0)}"
|
||||
TCBuildInfo(buildNumber, branch, startTime, finishTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get builds numbers in right order.
|
||||
internal fun <T> orderedValues(values: List<T>, buildElement: (T) -> String = { it -> it.toString() },
|
||||
skipMilestones: Boolean = false) =
|
||||
values.sortedWith(
|
||||
compareBy({ buildElement(it).substringBefore(".").toInt() },
|
||||
{ buildElement(it).substringAfter(".").substringBefore("-").toDouble() },
|
||||
{
|
||||
if (skipMilestones) 0
|
||||
else if (buildElement(it).substringAfter("-").startsWith("M"))
|
||||
buildElement(it).substringAfter("M").substringBefore("-").toInt()
|
||||
else
|
||||
Int.MAX_VALUE
|
||||
},
|
||||
{ buildElement(it).substringAfterLast("-").toDouble() }
|
||||
)
|
||||
)
|
||||
|
||||
// ElasticSearch connector for work with custom instance.
|
||||
internal val localHostElasticConnector = UrlNetworkConnector("http://localhost", 9200)
|
||||
// ElasticSearch connector for work with AWS instance.
|
||||
internal val awsElasticConnector = AWSNetworkConnector()
|
||||
internal val networkConnector = awsElasticConnector
|
||||
|
||||
fun urlParameterToBaseFormat(value: dynamic) =
|
||||
value.toString().replace("_", " ")
|
||||
|
||||
// Routing of requests to current server.
|
||||
fun router() {
|
||||
val express = require("express")
|
||||
val router = express.Router()
|
||||
val connector = ElasticSearchConnector(networkConnector)
|
||||
val benchmarksDispatcher = BenchmarksIndexesDispatcher(connector, "env.machine.os",
|
||||
listOf("Linux", "Mac OS X", "Windows 10")
|
||||
)
|
||||
val goldenIndex = GoldenResultsIndex(connector)
|
||||
val buildInfoIndex = BuildInfoIndex(connector)
|
||||
|
||||
router.get("/createMapping") { _, response ->
|
||||
buildInfoIndex.createMapping().then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch { _ ->
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
|
||||
// Get consistent build information in cases of rerunning the same build.
|
||||
suspend fun getConsistentBuildInfo(buildInfoInstance: BuildInfo, reports: List<BenchmarksReport>,
|
||||
rerunNumber: Int = 1): BuildInfo {
|
||||
var currentBuildInfo = buildInfoInstance
|
||||
if (buildExists(currentBuildInfo, buildInfoIndex)) {
|
||||
// Check if benchmarks aren't repeated.
|
||||
val existingBecnhmarks = benchmarksDispatcher.getBenchmarksList(currentBuildInfo.buildNumber,
|
||||
currentBuildInfo.agentInfo).await()
|
||||
val benchmarksToRegister = reports.map { it.benchmarks.keys }.flatten()
|
||||
if (existingBecnhmarks.toTypedArray().intersect(benchmarksToRegister).isNotEmpty()) {
|
||||
// Build was rerun.
|
||||
val buildNumber = "${currentBuildInfo.buildNumber}.$rerunNumber"
|
||||
currentBuildInfo = BuildInfo(buildNumber, currentBuildInfo.startTime, currentBuildInfo.endTime,
|
||||
currentBuildInfo.commitsList, currentBuildInfo.branch, currentBuildInfo.agentInfo)
|
||||
return getConsistentBuildInfo(currentBuildInfo, reports, rerunNumber + 1)
|
||||
}
|
||||
}
|
||||
return currentBuildInfo
|
||||
}
|
||||
|
||||
// Register build on Artifactory.
|
||||
router.post("/register") { request, response ->
|
||||
val register = BuildRegister.create(JSON.stringify(request.body))
|
||||
|
||||
// Get information from TeamCity.
|
||||
register.getBuildInformation().then { buildInfo ->
|
||||
register.sendTeamCityRequest(register.changesListUrl, true).then { changes ->
|
||||
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
||||
// Get artifact.
|
||||
val content = if(register.fileWithResult.contains("/"))
|
||||
UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET, register.fileWithResult)
|
||||
else register.sendTeamCityRequest(register.teamCityArtifactsUrl)
|
||||
content.then { resultsContent ->
|
||||
launch {
|
||||
val reportData = JsonTreeParser.parse(resultsContent)
|
||||
val reports = if (reportData is JsonArray) {
|
||||
reportData.map { BenchmarksReport.create(it as JsonObject) }
|
||||
} else listOf(BenchmarksReport.create(reportData as JsonObject))
|
||||
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||
val goldenResults = goldenResultPromise.await()
|
||||
// Register build information.
|
||||
var buildInfoInstance = getConsistentBuildInfo(
|
||||
BuildInfo(buildInfo.buildNumber, buildInfo.startTime, buildInfo.finishTime,
|
||||
commitsList, buildInfo.branch, reports[0].env.machine.os),
|
||||
reports
|
||||
)
|
||||
if (register.bundleSize != null) {
|
||||
// Add bundle size.
|
||||
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||
BenchmarkResult.Status.PASSED, register.bundleSize.toDouble(),
|
||||
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||
bundleSizeReport.buildNumber = buildInfoInstance.buildNumber
|
||||
benchmarksDispatcher.insert(bundleSizeReport, reports[0].env.machine.os).then { _ ->
|
||||
println("[BUNDLE] Success insert ${buildInfoInstance.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
val insertResults = reports.map {
|
||||
val benchmarksReport = SummaryBenchmarksReport(it).getBenchmarksReport()
|
||||
.normalizeBenchmarksSet(goldenResults)
|
||||
benchmarksReport.buildNumber = buildInfoInstance.buildNumber
|
||||
// Save results in database.
|
||||
benchmarksDispatcher.insert(benchmarksReport, benchmarksReport.env.machine.os)
|
||||
}
|
||||
if (!buildExists(buildInfoInstance, buildInfoIndex)) {
|
||||
buildInfoIndex.insert(buildInfoInstance).then { _ ->
|
||||
println("Success insert build information for ${buildInfoInstance.buildNumber}")
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
Promise.all(insertResults.toTypedArray()).then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register golden results to normalize on Artifactory.
|
||||
router.post("/registerGolden", { request, response ->
|
||||
val goldenResultsInfo: GoldenResultsInfo = JSON.parse<GoldenResultsInfo>(JSON.stringify(request.body))
|
||||
val goldenReport = goldenResultsInfo.toBenchmarksReport()
|
||||
goldenIndex.insert(goldenReport).then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
// Get builds description with additional information.
|
||||
router.get("/buildsDesc/:target", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
val target = request.params.target.toString().replace('_', ' ')
|
||||
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.branch != undefined) {
|
||||
branch = request.query.branch
|
||||
}
|
||||
if (request.query.type != undefined) {
|
||||
type = request.query.type
|
||||
}
|
||||
if (request.query.count != undefined) {
|
||||
buildsCountToShow = request.query.count.toString().toInt()
|
||||
}
|
||||
if (request.query.before != undefined) {
|
||||
beforeDate = decodeURIComponent(request.query.before)
|
||||
}
|
||||
if (request.query.after != undefined) {
|
||||
afterDate = decodeURIComponent(request.query.after)
|
||||
}
|
||||
}
|
||||
|
||||
getBuildsInfo(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate)
|
||||
.then { buildsInfo ->
|
||||
val buildNumbers = buildsInfo.map { it.buildNumber }
|
||||
// Get number of failed benchmarks for each build.
|
||||
benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures ->
|
||||
success(orderedValues(buildsInfo, { it -> it.buildNumber }, branch == "master").map {
|
||||
Build(it.buildNumber, it.startTime, it.endTime, it.branch,
|
||||
it.commitsList.serializeFields(), failures[it.buildNumber] ?: 0)
|
||||
})
|
||||
}.catch { errorResponse ->
|
||||
println("Error during getting failures numbers")
|
||||
println(errorResponse)
|
||||
reject()
|
||||
}
|
||||
}.catch {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get values of current metric.
|
||||
router.get("/metricValue/:target/:metric", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
val metric = request.params.metric
|
||||
val target = request.params.target.toString().replace('_', ' ')
|
||||
var samples: List<String> = emptyList()
|
||||
var aggregation = "geomean"
|
||||
var normalize = false
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var excludeNames: List<String> = emptyList()
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
|
||||
// Parse parameters from request if it exists.
|
||||
if (request.query != undefined) {
|
||||
if (request.query.samples != undefined) {
|
||||
samples = request.query.samples.toString().split(",").map { it.trim() }
|
||||
}
|
||||
if (request.query.agr != undefined) {
|
||||
aggregation = request.query.agr.toString()
|
||||
}
|
||||
if (request.query.normalize != undefined) {
|
||||
normalize = true
|
||||
}
|
||||
if (request.query.branch != undefined) {
|
||||
branch = request.query.branch
|
||||
}
|
||||
if (request.query.type != undefined) {
|
||||
type = request.query.type
|
||||
}
|
||||
if (request.query.exclude != undefined) {
|
||||
excludeNames = request.query.exclude.toString().split(",").map { it.trim() }
|
||||
}
|
||||
if (request.query.count != undefined) {
|
||||
buildsCountToShow = request.query.count.toString().toInt()
|
||||
}
|
||||
if (request.query.before != undefined) {
|
||||
beforeDate = decodeURIComponent(request.query.before)
|
||||
}
|
||||
if (request.query.after != undefined) {
|
||||
afterDate = decodeURIComponent(request.query.after)
|
||||
}
|
||||
}
|
||||
|
||||
getBuildsNumbers(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate).then { buildNumbers ->
|
||||
if (aggregation == "geomean") {
|
||||
// Get geometric mean for samples.
|
||||
benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize,
|
||||
excludeNames).then { geoMeansValues ->
|
||||
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||
}.catch { errorResponse ->
|
||||
println("Error during getting geometric mean")
|
||||
println(errorResponse)
|
||||
reject()
|
||||
}
|
||||
} else {
|
||||
benchmarksDispatcher.getSamples(metric, target, samples, buildsCountToShow, buildNumbers, normalize)
|
||||
.then { geoMeansValues ->
|
||||
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||
}.catch {
|
||||
println("Error during getting samples")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
}.catch {
|
||||
println("Error during getting builds information")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get branches for [target].
|
||||
router.get("/branches", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
distinctValues("branch", buildInfoIndex).then { results ->
|
||||
success(results)
|
||||
}.catch { errorMessage ->
|
||||
error(errorMessage.message ?: "Failed getting branches list.")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get build numbers for [target].
|
||||
router.get("/buildsNumbers/:target", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
distinctValues("buildNumber", buildInfoIndex).then { results ->
|
||||
success(results)
|
||||
}.catch { errorMessage ->
|
||||
println(errorMessage.message ?: "Failed getting branches list.")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Conert data and migrate it from Artifactory to DB.
|
||||
router.get("/migrate/:target", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
val targetPathName = target.replace(" ", "")
|
||||
var buildNumber: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.buildNumber != undefined) {
|
||||
buildNumber = request.query.buildNumber
|
||||
buildNumber = request.query.buildNumber
|
||||
}
|
||||
}
|
||||
getBuildsInfoFromArtifactory(targetPathName).then { buildInfo ->
|
||||
launch {
|
||||
val buildsDescription = buildInfo.lines().drop(1)
|
||||
var shouldConvert = buildNumber?.let { false } ?: true
|
||||
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||
val goldenResults = goldenResultPromise.await()
|
||||
val buildsSet = mutableSetOf<String>()
|
||||
buildsDescription.forEach {
|
||||
if (!it.isEmpty()) {
|
||||
val currentBuildNumber = it.substringBefore(',')
|
||||
if (!"\\d+(\\.\\d+)+(-M\\d)?-\\w+-\\d+(\\.\\d+)?".toRegex().matches(currentBuildNumber)) {
|
||||
error("Build number $currentBuildNumber differs from expected format. File with data for " +
|
||||
"target $target could be corrupted.")
|
||||
}
|
||||
if (!shouldConvert && buildNumber != null && buildNumber == currentBuildNumber) {
|
||||
shouldConvert = true
|
||||
}
|
||||
if (shouldConvert) {
|
||||
// Save data from Artifactory into database.
|
||||
val artifactoryUrlConnector = UrlNetworkConnector(artifactoryUrl)
|
||||
val fileName = "nativeReport.json"
|
||||
val accessFileUrl = "$targetPathName/$currentBuildNumber/$fileName"
|
||||
val extrenalFileName = if (target == "Linux") "externalReport.json" else "spaceFrameworkReport.json"
|
||||
val accessExternalFileUrl = "$targetPathName/$currentBuildNumber/$extrenalFileName"
|
||||
val infoParts = it.split(", ")
|
||||
if ((infoParts[3] == "master" || "eap" in currentBuildNumber || "release" in currentBuildNumber) &&
|
||||
currentBuildNumber !in buildsSet) {
|
||||
try {
|
||||
buildsSet.add(currentBuildNumber)
|
||||
val jsonReport = artifactoryUrlConnector.sendRequest(RequestMethod.GET, accessFileUrl).await()
|
||||
var reports = convert(jsonReport, currentBuildNumber, target)
|
||||
val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2],
|
||||
CommitsList.parse(infoParts[4]), infoParts[3], target)
|
||||
|
||||
val externalJsonReport = artifactoryUrlConnector.sendOptionalRequest(RequestMethod.GET, accessExternalFileUrl)
|
||||
.await()
|
||||
buildInfoIndex.insert(buildInfoRecord).then { _ ->
|
||||
println("[BUILD INFO] Success insert build number ${buildInfoRecord.buildNumber}")
|
||||
externalJsonReport?.let {
|
||||
var externalReports = convert(externalJsonReport.replace("circlet_iosX64", "SpaceFramework_iosX64"),
|
||||
currentBuildNumber, target)
|
||||
externalReports.forEach { externalReport ->
|
||||
val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport)
|
||||
.getBenchmarksReport().normalizeBenchmarksSet(goldenResults)
|
||||
extrenalAdditionalReport.buildNumber = currentBuildNumber
|
||||
benchmarksDispatcher.insert(extrenalAdditionalReport, target).then { _ ->
|
||||
println("[External] Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bundleSize = if (infoParts[10] != "-") infoParts[10] else null
|
||||
if (bundleSize != null) {
|
||||
// Add bundle size.
|
||||
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||
BenchmarkResult.Status.PASSED, bundleSize.toDouble(),
|
||||
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||
bundleSizeReport.buildNumber = currentBuildNumber
|
||||
benchmarksDispatcher.insert(bundleSizeReport, target).then { _ ->
|
||||
println("[BUNDLE] Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
|
||||
reports.forEach { report ->
|
||||
val summaryReport = SummaryBenchmarksReport(report).getBenchmarksReport()
|
||||
.normalizeBenchmarksSet(goldenResults)
|
||||
summaryReport.buildNumber = currentBuildNumber
|
||||
// Save results in database.
|
||||
benchmarksDispatcher.insert(summaryReport, target).then { _ ->
|
||||
println("Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/delete/:target", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
var buildNumber: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.buildNumber != undefined) {
|
||||
buildNumber = request.query.buildNumber
|
||||
}
|
||||
}
|
||||
benchmarksDispatcher.deleteBenchmarks(target, buildNumber).then {
|
||||
deleteBuildInfo(target, buildInfoIndex, buildNumber).then {
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/report/:target/:buildNumber", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
val buildNumber = request.params.buildNumber.toString()
|
||||
benchmarksDispatcher.getBenchmarksReports(buildNumber, target).then { reports ->
|
||||
response.send(reports.joinToString(", ", "[", "]"))
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/clear", { _, response ->
|
||||
CachableResponseDispatcher.clear()
|
||||
response.sendStatus(200)
|
||||
})
|
||||
|
||||
// Main page.
|
||||
router.get("/", { _, response ->
|
||||
response.render("index")
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
fun getBuildsInfoFromArtifactory(target: String): Promise<String> {
|
||||
val buildsFileName = "buildsSummary.csv"
|
||||
val artifactoryBuildsDirectory = "builds"
|
||||
return UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET,
|
||||
"$artifactoryBuildsDirectory/$target/$buildsFileName")
|
||||
}
|
||||
|
||||
fun BenchmarksReport.normalizeBenchmarksSet(dataForNormalization: Map<String, List<BenchmarkResult>>): BenchmarksReport {
|
||||
val resultBenchmarksList = benchmarks.map { benchmarksList ->
|
||||
benchmarksList.value.map {
|
||||
NormalizedMeanVarianceBenchmark(it.name, it.status, it.score, it.metric,
|
||||
it.runtimeInUs, it.repeat, it.warmup, (it as MeanVarianceBenchmark).variance,
|
||||
dataForNormalization[benchmarksList.key]?.get(0)?.score?.let { golden -> it.score / golden } ?: 0.0)
|
||||
}
|
||||
}.flatten()
|
||||
return BenchmarksReport(env, resultBenchmarksList, compiler)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.utils
|
||||
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.js.Promise
|
||||
|
||||
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||
}
|
||||
|
||||
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
|
||||
block.startCoroutine(Continuation(context) { result ->
|
||||
result.onFailure { exception ->
|
||||
throw exception
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
||||
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
kotlin {
|
||||
js {
|
||||
browser {
|
||||
binaries.executable()
|
||||
distribution {
|
||||
directory = new File("$projectDir/js/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../../benchmarks/shared/src'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir '../shared/src/main/kotlin'
|
||||
kotlin.srcDir '../src/main/kotlin-js'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
.chart {
|
||||
height: 400px;
|
||||
}
|
||||
.ct-legend {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
}
|
||||
.ct-legend li {
|
||||
position: relative;
|
||||
padding-left: 23px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 3px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
.ct-legend li:before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: '';
|
||||
border: 3px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.ct-legend li.inactive:before {
|
||||
background: transparent;
|
||||
}
|
||||
.ct-legend.ct-legend-inside {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.ct-legend.ct-legend-inside li{
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.ct-legend .ct-series-0:before {
|
||||
background-color: #d70206;
|
||||
border-color: #d70206;
|
||||
}
|
||||
.ct-legend .ct-series-1:before {
|
||||
background-color: gold;
|
||||
border-color: gold;
|
||||
}
|
||||
.ct-legend .ct-series-2:before {
|
||||
background-color: limegreen;
|
||||
border-color: limegreen;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-3:before {
|
||||
background-color: lightsalmon;
|
||||
border-color: lightsalmon;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-4:before {
|
||||
background-color: mediumorchid;
|
||||
border-color: mediumorchid;
|
||||
}
|
||||
.ct-legend .ct-series-5:before {
|
||||
background-color: navy;
|
||||
border-color: navy;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-6:before {
|
||||
background-color: darkcyan;
|
||||
border-color: darkcyan;
|
||||
}
|
||||
|
||||
.ct-series-b .ct-line,
|
||||
.ct-series-b .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: gold;
|
||||
}
|
||||
|
||||
.ct-series-c .ct-line,
|
||||
.ct-series-c .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: limegreen;
|
||||
}
|
||||
|
||||
.ct-series-d .ct-line,
|
||||
.ct-series-d .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: lightsalmon;
|
||||
}
|
||||
|
||||
.ct-series-e .ct-line,
|
||||
.ct-series-e .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: mediumorchid;
|
||||
}
|
||||
|
||||
.ct-series-f .ct-line,
|
||||
.ct-series-f .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: navy;
|
||||
}
|
||||
|
||||
.ct-series-g .ct-line,
|
||||
.ct-series-g .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: darkcyan;
|
||||
}
|
||||
.tooltip { pointer-events: none; }
|
||||
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Benchmarks report
|
||||
</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js">
|
||||
</script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bd-navbar" style="background-color:#161616;">
|
||||
<img src="https://dashboard.snapcraft.io/site_media/appmedia/2018/04/256px-kotlin-logo-svg.png" style="width:60px;height:60px;">
|
||||
|
||||
<span class="navbar-brand mb-0 h1">
|
||||
Benchmarks report
|
||||
</span>
|
||||
</header>
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupTarget">Target</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupTarget">
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="Mac_OS_X">Mac OS X</option>
|
||||
<option value="Windows_10">Windows 10</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupBuildType">Build type</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupBuildType">
|
||||
<option value="dev">Dev builds</option>
|
||||
<option value="day">Day</option>
|
||||
<option value="release">Releases</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupBranch">Branch</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupBranch">
|
||||
<option value="master">master</option>
|
||||
<option value="all">All branches</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">Highlighted build</span>
|
||||
</div>
|
||||
<input id="highligted_build" type="text" class="form-control" placeholder="" aria-label="build" aria-describedby="basic-addon1">
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="plusBtn">+</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="minusBtn">-</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="prevBtn"><-</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="nextBtn">-></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Normalized execution time</h4>
|
||||
<div id="exec_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Compile time</h4>
|
||||
<div id="compile_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Normalized code size</h4>
|
||||
<div id="codesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Bundle size</h4>
|
||||
<div id="bundlesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist-plugin-legend/0.6.2/chartist-plugin-legend.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.devbridge-autocomplete/1.4.9/jquery.autocomplete.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import kotlin.browser.*
|
||||
import org.w3c.fetch.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.buildInfo.Build
|
||||
import kotlin.js.*
|
||||
import kotlin.math.ceil
|
||||
import org.w3c.dom.*
|
||||
|
||||
// API for interop with JS library Chartist.
|
||||
external class ChartistPlugins {
|
||||
fun legend(data: dynamic): dynamic
|
||||
fun ctAxisTitle(data: dynamic): dynamic
|
||||
}
|
||||
|
||||
external object Chartist {
|
||||
class Svg(form: String, parameters: dynamic, chartArea: String)
|
||||
|
||||
val plugins: ChartistPlugins
|
||||
val Interpolation: dynamic
|
||||
fun Line(query: String, data: dynamic, options: dynamic): dynamic
|
||||
}
|
||||
|
||||
data class Commit(val revision: String, val developer: String)
|
||||
|
||||
fun sendGetRequest(url: String) = window.fetch(url, RequestInit("GET")).then { response ->
|
||||
if (!response.ok)
|
||||
error("Error during getting response from $url\n" +
|
||||
"${response}")
|
||||
else
|
||||
response.text()
|
||||
}.then { text -> text }
|
||||
|
||||
// Get data for chart in needed format.
|
||||
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>,
|
||||
classNames: Array<String>? = null): dynamic {
|
||||
val chartData: dynamic = object {}
|
||||
chartData["labels"] = labels.toTypedArray()
|
||||
chartData["series"] = valuesList.mapIndexed { index, it ->
|
||||
val series: dynamic = object {}
|
||||
series["data"] = it.toTypedArray()
|
||||
classNames?.let { series["className"] = classNames[index] }
|
||||
series
|
||||
}.toTypedArray()
|
||||
return chartData
|
||||
}
|
||||
|
||||
// Create object with options of chart.
|
||||
fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<String>? = null): dynamic {
|
||||
val chartOptions: dynamic = object {}
|
||||
chartOptions["fullWidth"] = true
|
||||
val paddingObject: dynamic = object {}
|
||||
paddingObject["right"] = 40
|
||||
chartOptions["chartPadding"] = paddingObject
|
||||
val axisXObject: dynamic = object {}
|
||||
axisXObject["offset"] = 40
|
||||
axisXObject["labelInterpolationFnc"] = { value, index, labels ->
|
||||
val labelsCount = 20
|
||||
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
|
||||
if (skipNumber > 1) {
|
||||
if (index % skipNumber == 0) value else null
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
chartOptions["axisX"] = axisXObject
|
||||
val axisYObject: dynamic = object {}
|
||||
axisYObject["offset"] = 90
|
||||
chartOptions["axisY"] = axisYObject
|
||||
val legendObject: dynamic = object {}
|
||||
legendObject["legendNames"] = samples
|
||||
classNames?.let { legendObject["classNames"] = classNames.sliceArray(0 until samples.size) }
|
||||
val titleObject: dynamic = object {}
|
||||
val axisYTitle: dynamic = object {}
|
||||
axisYTitle["axisTitle"] = yTitle
|
||||
axisYTitle["axisClass"] = "ct-axis-title"
|
||||
val titleOffset: dynamic = {}
|
||||
titleOffset["x"] = 15
|
||||
titleOffset["y"] = 15
|
||||
axisYTitle["offset"] = titleOffset
|
||||
axisYTitle["textAnchor"] = "middle"
|
||||
axisYTitle["flipTitle"] = true
|
||||
titleObject["axisY"] = axisYTitle
|
||||
val interpolationObject: dynamic = {}
|
||||
interpolationObject["fillHoles"] = true
|
||||
chartOptions["lineSmooth"] = Chartist.Interpolation.simple(interpolationObject)
|
||||
chartOptions["plugins"] = arrayOf(Chartist.plugins.legend(legendObject), Chartist.plugins.ctAxisTitle(titleObject))
|
||||
return chartOptions
|
||||
}
|
||||
|
||||
fun redirect(url: String) {
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
// Set customizations rules for chart.
|
||||
fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynamic, builds: List<Build?>,
|
||||
parameters: Map<String, String>) {
|
||||
chart.on("draw", { data ->
|
||||
var element = data.element
|
||||
if (data.type == "point") {
|
||||
val pointSize = 12
|
||||
val currentBuild = builds.get(data.index)
|
||||
currentBuild?.let { currentBuild ->
|
||||
// Higlight builds with failures.
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
val svgParameters: dynamic = object {}
|
||||
svgParameters["d"] = arrayOf("M", data.x, data.y - pointSize,
|
||||
"L", data.x - pointSize, data.y + pointSize / 2,
|
||||
"L", data.x + pointSize, data.y + pointSize / 2, "z").joinToString(" ")
|
||||
svgParameters["style"] = "fill:rgb(255,0,0);stroke-width:0"
|
||||
val triangle = Chartist.Svg("path", svgParameters, chartContainer)
|
||||
element = data.element.replace(triangle)
|
||||
} else if (currentBuild.buildNumber == parameters["build"]) {
|
||||
// Higlight choosen build.
|
||||
val svgParameters: dynamic = object {}
|
||||
svgParameters["x"] = data.x - pointSize / 2
|
||||
svgParameters["y"] = data.y - pointSize / 2
|
||||
svgParameters["height"] = pointSize
|
||||
svgParameters["width"] = pointSize
|
||||
svgParameters["style"] = "fill:rgb(0,0,255);stroke-width:0"
|
||||
val rectangle = Chartist.Svg("rect", svgParameters, "ct-point")
|
||||
element = data.element.replace(rectangle)
|
||||
}
|
||||
// Add tooltips.
|
||||
var shift = 1
|
||||
var previousBuild: Build? = null
|
||||
while (previousBuild == null && data.index - shift >= 0) {
|
||||
previousBuild = builds.get(data.index - shift)
|
||||
shift++
|
||||
}
|
||||
val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=" +
|
||||
"${currentBuild.buildNumber}:${parameters["target"]}" +
|
||||
"${previousBuild?.let {
|
||||
"&compareTo=${previousBuild.buildNumber}:${parameters["target"]}"
|
||||
} ?: ""}"
|
||||
val information = buildString {
|
||||
append("<a href=\"$linkToDetailedInfo\">${currentBuild.buildNumber}</a><br>")
|
||||
append("Value: ${data.value.y.toFixed(4)}<br>")
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
append("failures: ${currentBuild.failuresNumber}<br>")
|
||||
}
|
||||
append("branch: ${currentBuild.branch}<br>")
|
||||
append("date: ${currentBuild.date}<br>")
|
||||
append("time: ${currentBuild.formattedStartTime}-${currentBuild.formattedFinishTime}<br>")
|
||||
append("Commits:<br>")
|
||||
val commitsList = (JsonTreeParser.parse("{${currentBuild.commits}}") as JsonObject).getArray("commits").map {
|
||||
Commit(
|
||||
(it as JsonObject).getPrimitive("revision").content,
|
||||
(it as JsonObject).getPrimitive("developer").content
|
||||
)
|
||||
}
|
||||
val commits = if (commitsList.size > 3) commitsList.slice(0..2) else commitsList
|
||||
commits.forEach {
|
||||
append("${it.revision.substring(0, 7)} by ${it.developer}<br>")
|
||||
}
|
||||
if (commitsList.size > 3) {
|
||||
append("...")
|
||||
}
|
||||
}
|
||||
element._node.setAttribute("title", information)
|
||||
element._node.setAttribute("data-chart-tooltip", chartContainer)
|
||||
element._node.addEventListener("click", {
|
||||
redirect(linkToDetailedInfo)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
chart.on("created", {
|
||||
val currentChart = jquerySelector
|
||||
val parameters: dynamic = object {}
|
||||
parameters["selector"] = "[data-chart-tooltip=\"$chartContainer\"]"
|
||||
parameters["container"] = "#$chartContainer"
|
||||
parameters["html"] = true
|
||||
currentChart.tooltip(parameters)
|
||||
})
|
||||
}
|
||||
|
||||
var buildsNumberToShow: Int = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
|
||||
external fun decodeURIComponent(url: String): String
|
||||
external fun encodeURIComponent(url: String): String
|
||||
|
||||
fun getDatesComponents() = "${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}" +
|
||||
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||
val zoomRatio = 2
|
||||
|
||||
// Get parameters from request.
|
||||
val url = window.location.href
|
||||
val parametersPart = url.substringAfter("?").split('&')
|
||||
val parameters = mutableMapOf("target" to "Linux", "type" to "dev", "build" to "", "branch" to "master")
|
||||
parametersPart.forEach {
|
||||
val parsedParameter = it.split("=", limit = 2)
|
||||
if (parsedParameter.size == 2) {
|
||||
val (key, value) = parsedParameter
|
||||
parameters[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
buildsNumberToShow = parameters["count"]?.toInt() ?: buildsNumberToShow
|
||||
beforeDate = parameters["before"]?.let{ decodeURIComponent(it)}
|
||||
afterDate = parameters["after"]?.let{ decodeURIComponent(it)}
|
||||
|
||||
// Get branches.
|
||||
val branchesUrl = "$serverUrl/branches"
|
||||
sendGetRequest(branchesUrl).then { response ->
|
||||
val branches: Array<String> = JSON.parse(response)
|
||||
// Add release branches to selector.
|
||||
branches.filter { it != "master" }.forEach {
|
||||
if ("v(\\d|\\.)+(-M\\d)?-fixes".toRegex().matches(it)) {
|
||||
val option = Option(it, it)
|
||||
js("$('#inputGroupBranch')").append(js("$(option)"))
|
||||
}
|
||||
}
|
||||
document.querySelector("#inputGroupBranch [value=\"${parameters["branch"]}\"]")?.setAttribute("selected", "true")
|
||||
}
|
||||
|
||||
|
||||
// Fill autocomplete list with build numbers.
|
||||
val buildsNumbersUrl = "$serverUrl/buildsNumbers/${parameters["target"]}"
|
||||
sendGetRequest(buildsNumbersUrl).then { response ->
|
||||
val buildsNumbers: Array<String> = JSON.parse(response)
|
||||
val autocompleteParameters: dynamic = object {}
|
||||
autocompleteParameters["lookup"] = buildsNumbers
|
||||
autocompleteParameters["onSelect"] = { suggestion ->
|
||||
if (suggestion.value != parameters["build"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||
"${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
}
|
||||
}
|
||||
js("$( \"#highligted_build\" )").autocomplete(autocompleteParameters)
|
||||
js("$('#highligted_build')").change({ value ->
|
||||
val newValue = js("$(this).val()").toString()
|
||||
if (newValue.isEmpty() || newValue in buildsNumbers) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||
"${if (newValue.isEmpty()) "" else "&build=$newValue"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Change inputs values connected with parameters and add events listeners.
|
||||
document.querySelector("#inputGroupTarget [value=\"${parameters["target"]}\"]")?.setAttribute("selected", "true")
|
||||
document.querySelector("#inputGroupBuildType [value=\"${parameters["type"]}\"]")?.setAttribute("selected", "true")
|
||||
(document.getElementById("highligted_build") as HTMLInputElement).value = parameters["build"]!!
|
||||
|
||||
// Add onChange events for fields.
|
||||
// Don't use AJAX to have opportunity to share results with simple links.
|
||||
js("$('#inputGroupTarget')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["target"]) {
|
||||
val newLink = "http://${window.location.host}/?target=$newValue&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
js("$('#inputGroupBuildType')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["type"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=$newValue&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
js("$('#inputGroupBranch')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["branch"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=$newValue" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
val platformSpecificBenchs = if (parameters["target"] == "Mac_OS_X") ",FrameworkBenchmarksAnalyzer,SpaceFramework_iosX64" else
|
||||
if (parameters["target"] == "Linux") ",kotlinx.coroutines" else ""
|
||||
|
||||
// Collect information for charts library.
|
||||
val valuesToShow = mapOf("EXECUTION_TIME" to listOf(mapOf(
|
||||
"normalize" to "true"
|
||||
)),
|
||||
"COMPILE_TIME" to listOf(mapOf(
|
||||
"samples" to "HelloWorld,Videoplayer$platformSpecificBenchs",
|
||||
"agr" to "samples"
|
||||
)),
|
||||
"CODE_SIZE" to listOf(mapOf(
|
||||
"normalize" to "true",
|
||||
"exclude" to if (parameters["target"] == "Linux")
|
||||
"kotlinx.coroutines"
|
||||
else if (parameters["target"] == "Mac_OS_X")
|
||||
"SpaceFramework_iosX64"
|
||||
else ""
|
||||
), if (platformSpecificBenchs.isNotEmpty()) mapOf(
|
||||
"normalize" to "true",
|
||||
"agr" to "samples",
|
||||
"samples" to platformSpecificBenchs.removePrefix(",")
|
||||
) else null).filterNotNull(),
|
||||
"BUNDLE_SIZE" to listOf(mapOf("samples" to "KotlinNative",
|
||||
"agr" to "samples"))
|
||||
)
|
||||
|
||||
var execData = listOf<String>() to listOf<List<Double?>>()
|
||||
var compileData = listOf<String>() to listOf<List<Double?>>()
|
||||
var codeSizeData = listOf<String>() to listOf<List<Double?>>()
|
||||
var bundleSizeData = listOf<String>() to listOf<List<Int?>>()
|
||||
|
||||
val sizeClassNames = arrayOf("ct-series-e", "ct-series-f", "ct-series-g")
|
||||
|
||||
// Draw charts.
|
||||
var execChart: dynamic = null
|
||||
var compileChart: dynamic = null
|
||||
var codeSizeChart: dynamic = null
|
||||
var bundleSizeChart: dynamic = null
|
||||
|
||||
val descriptionUrl = "$serverUrl/buildsDesc/${parameters["target"]}?type=${parameters["type"]}" +
|
||||
"${if (parameters["branch"] != "all") "&branch=${parameters["branch"]}" else ""}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
|
||||
val metricUrl = "$serverUrl/metricValue/${parameters["target"]}/"
|
||||
|
||||
// Get builds description.
|
||||
val buildsInfoPromise = sendGetRequest(descriptionUrl).then { response ->
|
||||
val buildsInfo = response as String
|
||||
val data = JsonTreeParser.parse(buildsInfo)
|
||||
if (data !is JsonArray) {
|
||||
error("Response is expected to be an array.")
|
||||
}
|
||||
data.jsonArray.map {
|
||||
val element = it as JsonElement
|
||||
if (element.isNull) null else Build.create(element as JsonObject)
|
||||
}
|
||||
}
|
||||
|
||||
// Send requests to get all needed metric values.
|
||||
valuesToShow.map { (metric, listOfSettings) ->
|
||||
val resultValues = listOfSettings.map { settings ->
|
||||
val getParameters = with(StringBuilder()) {
|
||||
if (settings.isNotEmpty()) {
|
||||
append("?")
|
||||
}
|
||||
var prefix = ""
|
||||
settings.forEach { (key, value) ->
|
||||
if (value.isNotEmpty()) {
|
||||
append("$prefix$key=$value")
|
||||
prefix = "&"
|
||||
}
|
||||
}
|
||||
toString()
|
||||
}
|
||||
val branchParameter = if (parameters["branch"] != "all")
|
||||
(if (getParameters.isEmpty()) "?" else "&") + "branch=${parameters["branch"]}"
|
||||
else ""
|
||||
|
||||
val url = "$metricUrl$metric$getParameters$branchParameter${
|
||||
if (parameters["type"] != "all")
|
||||
(if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}"
|
||||
else ""
|
||||
}&count=$buildsNumberToShow${getDatesComponents()}"
|
||||
sendGetRequest(url)
|
||||
}.toTypedArray()
|
||||
|
||||
// Get metrics values for charts.
|
||||
Promise.all(resultValues).then { responses ->
|
||||
val valuesList = responses.map { response ->
|
||||
val results = (JsonTreeParser.parse(response) as JsonArray).map {
|
||||
(it as JsonObject).getPrimitive("first").content to
|
||||
it.getArray("second").map { (it as JsonPrimitive).doubleOrNull }
|
||||
}
|
||||
|
||||
val labels = results.map { it.first }
|
||||
val values = results[0]?.second?.size?.let { (0..it - 1).map { i -> results.map { it.second[i] } } }
|
||||
?: emptyList()
|
||||
labels to values
|
||||
}
|
||||
val labels = valuesList[0].first
|
||||
val values = valuesList.map { it.second }.reduce { acc, valuesPart -> acc + valuesPart }
|
||||
|
||||
when (metric) {
|
||||
// Update chart with gotten data.
|
||||
"COMPILE_TIME" -> {
|
||||
compileData = labels to values.map { it.map { it?.let { it / 1000 } } }
|
||||
compileChart = Chartist.Line("#compile_chart",
|
||||
getChartData(labels, compileData.second),
|
||||
getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(),
|
||||
"Time, milliseconds"))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
}
|
||||
}
|
||||
"EXECUTION_TIME" -> {
|
||||
execData = labels to values
|
||||
execChart = Chartist.Line("#exec_chart",
|
||||
getChartData(labels, execData.second),
|
||||
getChartOptions(arrayOf("Geometric Mean"), "Normalized time"))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
|
||||
execChart.update(getChartData(execData.first, execData.second))
|
||||
}
|
||||
}
|
||||
"CODE_SIZE" -> {
|
||||
codeSizeData = labels to values
|
||||
codeSizeChart = Chartist.Line("#codesize_chart",
|
||||
getChartData(labels, codeSizeData.second),
|
||||
getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',')
|
||||
.filter { it.isNotEmpty() },
|
||||
"Normalized size",
|
||||
arrayOf("ct-series-4", "ct-series-5", "ct-series-6")))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
|
||||
}
|
||||
}
|
||||
"BUNDLE_SIZE" -> {
|
||||
bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } }
|
||||
bundleSizeChart = Chartist.Line("#bundlesize_chart",
|
||||
getChartData(labels,
|
||||
bundleSizeData.second, sizeClassNames),
|
||||
getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4")))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
|
||||
}
|
||||
}
|
||||
else -> error("No chart for metric $metric")
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Update all charts with using same data.
|
||||
val updateAllCharts: () -> Unit = {
|
||||
execChart.update(getChartData(execData.first, execData.second))
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
|
||||
}
|
||||
|
||||
js("$('#plusBtn')").click({
|
||||
buildsNumberToShow =
|
||||
if (buildsNumberToShow / zoomRatio > zoomRatio) {
|
||||
buildsNumberToShow / zoomRatio
|
||||
} else {
|
||||
buildsNumberToShow
|
||||
}
|
||||
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
Unit
|
||||
})
|
||||
|
||||
js("$('#minusBtn')").click({
|
||||
buildsNumberToShow = buildsNumberToShow * zoomRatio
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
Unit
|
||||
})
|
||||
|
||||
js("$('#prevBtn')").click({
|
||||
buildsInfoPromise.then { builds ->
|
||||
beforeDate = builds.firstOrNull()?.startTime
|
||||
afterDate = null
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
"${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
js("$('#nextBtn')").click({
|
||||
buildsInfoPromise.then { builds ->
|
||||
beforeDate = null
|
||||
afterDate = builds.lastOrNull()?.startTime
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
// Auto reload.
|
||||
parameters["refresh"]?.let {
|
||||
// Set event.
|
||||
window.setInterval({
|
||||
window.location.reload()
|
||||
}, it.toInt() * 1000)
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This script scans Xcode directores for unknown frameworks
|
||||
# and tries to add them as Kotlin/Native def files.
|
||||
#
|
||||
# Note that some frameworks are unsupported (e.g. swift-only) and marked as .disabled.
|
||||
# Others may be supported and manual adjustment required (.attention_required).
|
||||
# Don't consider this script as a fully automatic solution. It tries its best
|
||||
# to identify unsupported frameworks correctly, but the result still should be validated.
|
||||
#
|
||||
# jq is required to run the script. It can be installed via `brew install jq`.
|
||||
#
|
||||
# Args:
|
||||
# 1. Platform name: ios, tvos, osx or watchos
|
||||
# 2. Path to Kotlin/Native sources.
|
||||
set -e
|
||||
|
||||
NATIVE_SRCDIR=$2
|
||||
|
||||
case $1 in
|
||||
tvos*)
|
||||
DEV_SDK=$(xcrun --show-sdk-path --sdk appletvos)
|
||||
SIM_SDK=$(xcrun --show-sdk-path --sdk appletvsimulator)
|
||||
OS_NAME="tvOS"
|
||||
DEVICES=("tvos_arm64")
|
||||
SIMULATORS=("tvos_x64")
|
||||
;;
|
||||
ios*)
|
||||
DEV_SDK=$(xcrun --show-sdk-path --sdk iphoneos)
|
||||
SIM_SDK=$(xcrun --show-sdk-path --sdk iphonesimulator)
|
||||
OS_NAME="iOS"
|
||||
DEVICES=("ios_arm32" "ios_arm64")
|
||||
SIMULATORS=("ios_x64")
|
||||
;;
|
||||
watchos*)
|
||||
DEV_SDK=$(xcrun --show-sdk-path --sdk watchos)
|
||||
SIM_SDK=$(xcrun --show-sdk-path --sdk watchsimulator)
|
||||
OS_NAME="watchOS"
|
||||
DEVICES=("watchos_arm32" "watchos_arm64")
|
||||
SIMULATORS=("watchos_i386")
|
||||
;;
|
||||
osx*)
|
||||
DEV_SDK=$(xcrun --show-sdk-path)
|
||||
SIM_SDK=$DEV_SDK
|
||||
OS_NAME="macOS"
|
||||
;;
|
||||
*)
|
||||
echo "Expected one of: osx ios watchos tvos. Got: $1"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
DEFS=$NATIVE_SRCDIR/platformLibs/src/platform/$1
|
||||
FRAMEWORKS_DEV=$DEV_SDK/System/Library/Frameworks
|
||||
FRAMEWORKS_SIM=$SIM_SDK/System/Library/Frameworks
|
||||
|
||||
DEFS_FILE=$(mktemp)
|
||||
FRAMEWORKS_DEV_FILE=$(mktemp)
|
||||
FRAMEWORKS_SIM_FILE=$(mktemp)
|
||||
FRAMEWORKS_COMMON_FILE=$(mktemp)
|
||||
FRAMEWORKS_DEV_ONLY_FILE=$(mktemp)
|
||||
FRAMEWORKS_SIM_ONLY_FILE=$(mktemp)
|
||||
ls $DEFS | grep .def | cut -d '.' -f 1 > $DEFS_FILE
|
||||
ls $FRAMEWORKS_DEV | grep .framework | cut -d '.' -f 1 > $FRAMEWORKS_DEV_FILE
|
||||
ls $FRAMEWORKS_SIM | grep .framework | cut -d '.' -f 1 > $FRAMEWORKS_SIM_FILE
|
||||
|
||||
comm -12 $FRAMEWORKS_DEV_FILE $FRAMEWORKS_SIM_FILE > $FRAMEWORKS_COMMON_FILE
|
||||
comm -13 $FRAMEWORKS_DEV_FILE $FRAMEWORKS_SIM_FILE > $FRAMEWORKS_SIM_ONLY_FILE
|
||||
comm -23 $FRAMEWORKS_DEV_FILE $FRAMEWORKS_SIM_FILE > $FRAMEWORKS_DEV_ONLY_FILE
|
||||
ABSENT_COMMON=$(comm -13 $DEFS_FILE $FRAMEWORKS_COMMON_FILE)
|
||||
ABSENT_SIM=$(comm -13 $DEFS_FILE $FRAMEWORKS_SIM_ONLY_FILE)
|
||||
ABSENT_DEV=$(comm -13 $DEFS_FILE $FRAMEWORKS_DEV_ONLY_FILE)
|
||||
|
||||
rm $DEFS_FILE
|
||||
rm $FRAMEWORKS_DEV_FILE
|
||||
rm $FRAMEWORKS_SIM_FILE
|
||||
rm $FRAMEWORKS_COMMON_FILE
|
||||
rm $FRAMEWORKS_DEV_ONLY_FILE
|
||||
rm $FRAMEWORKS_SIM_ONLY_FILE
|
||||
|
||||
AVAILABLE=()
|
||||
AVAILABLE_ON_DEV=()
|
||||
AVAILABLE_ON_SIM=()
|
||||
UNAVAILABLE=()
|
||||
SWIFT_ONLY=()
|
||||
DRIVER_KIT=()
|
||||
OS_UNSUPPORTED=()
|
||||
JSON=$(mktemp)
|
||||
|
||||
# Use information about framework on developer.apple.com to put it into
|
||||
# appropriate bucket.
|
||||
function classify {
|
||||
FRAMEWORK_NAME=$1
|
||||
KIND=$2
|
||||
URL=https://developer.apple.com/tutorials/data/documentation
|
||||
# Try to force Objective-C documentation. Swift is used by default.
|
||||
STATUS=$(curl -s -o $JSON -w "%{http_code}" "$URL/$FRAMEWORK_NAME.json?language=objc")
|
||||
# Some old frameworks don't have a documentation page.
|
||||
if [[ $STATUS -ne 200 ]]
|
||||
then
|
||||
UNAVAILABLE+=($FRAMEWORK_NAME)
|
||||
return
|
||||
fi
|
||||
# DriverKit is C++. Drop it.
|
||||
if [[ $(cat $JSON | jq '.metadata.platforms[] | .name' | grep DriverKit) ]]
|
||||
then
|
||||
DRIVER_KIT+=($FRAMEWORK_NAME)
|
||||
return
|
||||
fi
|
||||
# Sometimes framework is present in SDK directory, but actually it isn't supported on
|
||||
# current OS.
|
||||
if [[ ! $(cat $JSON | jq '.metadata.platforms[] | .name' | grep $OS_NAME) ]]
|
||||
then
|
||||
OS_UNSUPPORTED+=($FRAMEWORK_NAME)
|
||||
return
|
||||
fi
|
||||
LANG=$(cat $JSON | jq '.identifier.interfaceLanguage' | grep swift || true)
|
||||
if [[ $LANG = \"swift\" ]]
|
||||
then
|
||||
SWIFT_ONLY+=($FRAMEWORK_NAME)
|
||||
return
|
||||
fi
|
||||
|
||||
case $KIND in
|
||||
device*)
|
||||
AVAILABLE_ON_DEV+=($FRAMEWORK_NAME)
|
||||
;;
|
||||
simulator*)
|
||||
AVAILABLE_ON_SIM+=($FRAMEWORK_NAME)
|
||||
;;
|
||||
common*)
|
||||
AVAILABLE+=($FRAMEWORK_NAME)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
for framework in $ABSENT_COMMON
|
||||
do
|
||||
classify $framework common
|
||||
done
|
||||
|
||||
for framework in $ABSENT_DEV
|
||||
do
|
||||
classify $framework device
|
||||
done
|
||||
|
||||
for framework in $ABSENT_SIM
|
||||
do
|
||||
classify $framework simulator
|
||||
done
|
||||
|
||||
rm $JSON
|
||||
|
||||
PLATFORM_LIBS=$NATIVE_SRCDIR/platformLibs/src/platform/$1
|
||||
|
||||
function create_def_content {
|
||||
FRAMEWORK=$1
|
||||
DEF_FILE=$2
|
||||
echo "language = Objective-C" >> $DEF_FILE
|
||||
echo "package = platform.$FRAMEWORK" >> $DEF_FILE
|
||||
case $3 in
|
||||
devices*)
|
||||
TARGETS=("${DEVICES[@]}")
|
||||
;;
|
||||
simulators*)
|
||||
TARGETS=("${SIMULATORS[@]}")
|
||||
;;
|
||||
*)
|
||||
TARGETS=("")
|
||||
;;
|
||||
esac
|
||||
for target in "${TARGETS[@]}"
|
||||
do
|
||||
if [[ -z "$target" ]]
|
||||
then
|
||||
SUFFIX=""
|
||||
else
|
||||
SUFFIX=".$target"
|
||||
fi
|
||||
echo "" >> $DEF_FILE
|
||||
echo "modules$SUFFIX = $FRAMEWORK" >> $DEF_FILE
|
||||
echo "compilerOpts$SUFFIX = -framework $FRAMEWORK" >> $DEF_FILE
|
||||
echo "linkerOpts$SUFFIX = -framework $FRAMEWORK" >> $DEF_FILE
|
||||
done
|
||||
}
|
||||
|
||||
function create_def {
|
||||
FRAMEWORK=$1
|
||||
TARGETS=$2
|
||||
DEF_FILE=$PLATFORM_LIBS/$FRAMEWORK.def
|
||||
touch $DEF_FILE
|
||||
create_def_content $FRAMEWORK $DEF_FILE $TARGETS
|
||||
echo "Created $DEF_FILE"
|
||||
}
|
||||
|
||||
# Creates def file with additional suffix
|
||||
# and adds an explanation comment.
|
||||
function create_disabled {
|
||||
FRAMEWORK=$1
|
||||
REASON=$2
|
||||
EXTENSION=$3
|
||||
TARGETS=$4
|
||||
DEF_FILE=$PLATFORM_LIBS/$FRAMEWORK.def.$EXTENSION
|
||||
touch $DEF_FILE
|
||||
create_def_content $FRAMEWORK $DEF_FILE $TARGETS
|
||||
echo "#Disabled: $REASON" >> $DEF_FILE
|
||||
echo "Created $DEF_FILE"
|
||||
}
|
||||
|
||||
if [ ${#AVAILABLE[@]} -ne 0 ]
|
||||
then
|
||||
echo "New frameworks added."
|
||||
fi
|
||||
for framework in "${AVAILABLE[@]}"
|
||||
do
|
||||
if [[ -d $DEV_SDK/System/Library/Frameworks/$framework.framework/Modules ]]
|
||||
then
|
||||
create_def $framework
|
||||
else
|
||||
create_disabled $framework "Framework without module" attention_required
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#AVAILABLE_ON_SIM[@]} -ne 0 ]
|
||||
then
|
||||
echo "The following frameworks are available only for simulators."
|
||||
fi
|
||||
for framework in "${AVAILABLE_ON_SIM[@]}"
|
||||
do
|
||||
create_disabled $framework "Check that framework is not available for devices" attention_required simulators
|
||||
done
|
||||
|
||||
if [ ${#AVAILABLE_ON_DEV[@]} -ne 0 ]
|
||||
then
|
||||
echo "The following frameworks are available only for devices."
|
||||
fi
|
||||
for framework in "${AVAILABLE_ON_DEV[@]}"
|
||||
do
|
||||
create_disabled $framework "Check that framework is not available for simulators" attention_required devices
|
||||
done
|
||||
|
||||
|
||||
if [ ${#UNAVAILABLE[@]} -ne 0 ]
|
||||
then
|
||||
echo "Documentation for the following frameworks is not directly accessible."
|
||||
echo "They may be deprecated or available by different name."
|
||||
echo "For example, AppClip is accessed as app_clips ¯\_(ツ)_/¯."
|
||||
fi
|
||||
for framework in "${UNAVAILABLE[@]}"
|
||||
do
|
||||
create_disabled $framework "Unavailable" attention_required
|
||||
done
|
||||
|
||||
if [ ${#SWIFT_ONLY[@]} -ne 0 ]
|
||||
then
|
||||
echo "The following frameworks doesn't provide Objective-C API."
|
||||
fi
|
||||
for framework in "${SWIFT_ONLY[@]}"
|
||||
do
|
||||
create_disabled $framework "Swift-only framework" disabled
|
||||
done
|
||||
|
||||
if [ ${#DRIVER_KIT[@]} -ne 0 ]
|
||||
then
|
||||
echo "The following frameworks are from DriverKit."
|
||||
fi
|
||||
for framework in "${DRIVER_KIT[@]}"
|
||||
do
|
||||
create_disabled $framework "part of DriverKit" disabled
|
||||
done
|
||||
|
||||
if [ ${#OS_UNSUPPORTED[@]} -ne 0 ]
|
||||
then
|
||||
echo "The following frameworks are not officially provided for $1."
|
||||
fi
|
||||
for framework in "${OS_UNSUPPORTED[@]}"
|
||||
do
|
||||
create_disabled $framework "Not officially available for $1" disabled
|
||||
done
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# "brew install coreutils" for grealpath.
|
||||
KONAN_TOOLCHAIN_VERSION=xcode_12_0
|
||||
SDKS="macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"
|
||||
TARBALL_macosx=target-sysroot-$KONAN_TOOLCHAIN_VERSION-macos_x64
|
||||
TARBALL_iphoneos=target-sysroot-$KONAN_TOOLCHAIN_VERSION-ios_arm64
|
||||
TARBALL_iphonesimulator=target-sysroot-$KONAN_TOOLCHAIN_VERSION-ios_x64
|
||||
TARBALL_appletvos=target-sysroot-$KONAN_TOOLCHAIN_VERSION-tvos_arm64
|
||||
TARBALL_appletvsimulator=target-sysroot-$KONAN_TOOLCHAIN_VERSION-tvos_x64
|
||||
TARBALL_watchos=target-sysroot-$KONAN_TOOLCHAIN_VERSION-watchos_arm32
|
||||
TARBALL_watchsimulator=target-sysroot-$KONAN_TOOLCHAIN_VERSION-watchos_x86
|
||||
TARBALL_xcode=target-toolchain-$KONAN_TOOLCHAIN_VERSION-macos_x64
|
||||
TARBALL_xcode_addon=xcode-addon-$KONAN_TOOLCHAIN_VERSION-macos_x64
|
||||
OUT=`pwd`
|
||||
|
||||
for s in $SDKS; do
|
||||
p=`xcrun --sdk $s --show-sdk-path`
|
||||
p=`grealpath $p`
|
||||
tarball_var=TARBALL_${s}
|
||||
tarball=${!tarball_var}
|
||||
echo "Packing SDK $s as $OUT/$tarball.tar.gz..."
|
||||
$SHELL -c "tar czf $OUT/$tarball.tar.gz -C $p -s '/^\./$tarball/HS' ."
|
||||
done
|
||||
|
||||
t=`xcrun -f ld`
|
||||
t=`dirname $t`
|
||||
t=`grealpath $t/../..`
|
||||
tarball=$TARBALL_xcode
|
||||
echo "Packing toolchain $OUT/$tarball.tar.gz..."
|
||||
$SHELL -c "tar czf $OUT/$tarball.tar.gz -C $t -s '/^\./$tarball/HS' ."
|
||||
|
||||
t=`xcrun -f bitcode-build-tool`
|
||||
t=`dirname $t`
|
||||
t=`grealpath $t/..`
|
||||
tarball=$TARBALL_xcode_addon
|
||||
echo "Packing additional tools $OUT/$tarball.tar.gz..."
|
||||
$SHELL -c "tar czf $OUT/$tarball.tar.gz -C $t -s '/^\./$tarball/HS' ."
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
KONAN_TOOLCHAIN_VERSION=1
|
||||
TARBALL_zephyr_arm=target-sysroot-$KONAN_TOOLCHAIN_VERSION-zephyr-arm
|
||||
OUT=`pwd`
|
||||
|
||||
if [ -z "ZEPHYR_SDK_INSTALL_DIR" ]; then
|
||||
echo "Using default Zephyr SDK install location"
|
||||
export ZEPHYR_SDK_INSTALL_DIR="/opt/zephyr-sdk"
|
||||
fi
|
||||
|
||||
sdk=armv5-zephyr-eabi
|
||||
p=$ZEPHYR_SDK_INSTALL_DIR/sysroots/$sdk/usr
|
||||
echo "Packing SDK $sdk as $OUT/$TARBALL_zephyr_arm.tar.gz..."
|
||||
tar -czvf $OUT/$TARBALL_zephyr_arm.tar.gz -C $p .
|
||||
Reference in New Issue
Block a user