GradleUtils/src/dorkbox/gradle/StaticMethodsAndTools.kt

856 lines
35 KiB
Kotlin

/*
* Copyright 2021 dorkbox, llc
*
* 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 dorkbox.gradle
import dorkbox.gradle.deps.DependencyScanner
import dorkbox.gradle.jpms.JavaXConfiguration
import dorkbox.gradle.jpms.SourceSetContainer2
import org.gradle.api.*
import org.gradle.api.execution.TaskExecutionGraph
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.specs.Specs
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.security.MessageDigest
import java.util.*
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.declaredMemberProperties
@Suppress("unused", "MemberVisibilityCanBePrivate", "ObjectLiteralToLambda")
open class StaticMethodsAndTools(private val project: Project) {
companion object {
const val defaultKotlinVersion = "1.6.10"
/**
* If the kotlin plugin is applied, and there is a compileKotlin task.. Then kotlin is enabled
* NOTE: This can ONLY be called from a task, it cannot be called globally!
*/
fun hasKotlin(project: Project, debug: Boolean = false): Boolean {
try {
// check if plugin is available
project.plugins.findPlugin("org.jetbrains.kotlin.jvm") ?: return false
if (debug) println("\tHas kotlin plugin")
// this will check if the task exists, and throw an exception if it does not or return false
project.tasks.named("compileKotlin", KotlinCompile::class.java).orNull ?: return false
if (debug) println("\tHas compile kotlin task")
// check to see if we have any kotlin file
val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer
val main = sourceSets.getByName("main")
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("main").kotlin
if (debug) {
println("\tmain dirs: ${main.java.srcDirs}")
println("\tkotlin dirs: ${kotlin.srcDirs}")
project.buildFile.parentFile.walkTopDown().filter { it.extension == "kt" }.forEach {
println("\t\t$it")
}
}
val files = main.java.srcDirs + kotlin.srcDirs
files.forEach { srcDir ->
val kotlinFile = srcDir.walkTopDown().find { it.extension == "kt" }
if (kotlinFile?.exists() == true) {
if (debug) println("\t Has kotlin file: $kotlinFile")
return true
}
}
} catch (e: Exception) {
if (debug) e.printStackTrace()
}
return false
}
internal fun idea(project: Project, configure: org.gradle.plugins.ide.idea.model.IdeaModel.() -> Unit): Unit =
project.extensions.configure("idea", configure)
// required to make sure the plugins are correctly applied. ONLY applying it to the project WILL NOT work.
// The plugin must also be applied to the root project
// https://discuss.gradle.org/t/can-a-plugin-itself-add-buildscript-dependencies-and-then-apply-a-plugin/25039/4
internal fun apply(project: Project, id: String) {
if (project.rootProject.pluginManager.findPlugin(id) == null) {
project.rootProject.pluginManager.apply(id)
}
if (project.pluginManager.findPlugin(id) == null) {
project.pluginManager.apply(id)
}
}
}
val isUnix = org.gradle.internal.os.OperatingSystem.current().isUnix
val isLinux = org.gradle.internal.os.OperatingSystem.current().isLinux
val isMac = org.gradle.internal.os.OperatingSystem.current().isMacOsX
val isWindows = org.gradle.internal.os.OperatingSystem.current().isWindows
private var fixedSWT = false
// this is lazy, because it MUST be run from a task!
val hasKotlin: Boolean by lazy { hasKotlin(project) }
init {
apply(project, "idea")
}
/**
* Maps the property (key/value) pairs of a property file onto the specified target object. Also maps fields in the targetObject to the
* project, if they have the same name relationship (ie: field name is "version", project method is "setVersion")
*/
fun load(propertyFile: String, targetObject: Any) {
val kClass = targetObject::class
val propsFile = File(propertyFile).normalize()
if (propsFile.canRead()) {
println("\tLoading custom property data from: [$propsFile]")
val props = Properties()
propsFile.inputStream().use {
props.load(it)
}
val extraProperties = kClass.declaredMemberProperties.filterIsInstance<KMutableProperty<String>>()
val assignedExtraProperties = kClass.declaredMemberProperties.filterIsInstance<KProperty<String>>()
// project functions that can be called for setting properties
val propertyFunctions = Project::class.declaredMemberFunctions.filter { it.parameters.size == 2 }
// THREE possibilities for property registration or assignment
// 1) we have MANUALLY defined this property (via the configuration object)
// 1) gradleUtil properties loaded first
// -> gradleUtil's adds a function that everyone else (plugin/task) can call to get values from properties
// 2) gradleUtil properties loaded last
// -> others add a function that gradleUtil's call to set values from properties
// get the module loaded registration functions (if they exist)
val loaderFunctions: ArrayList<Plugin<Pair<String, String>>>?
if (project.extensions.extraProperties.has("property_loader_functions")) {
@Suppress("UNCHECKED_CAST")
loaderFunctions = project.extensions.extraProperties["property_loader_functions"] as ArrayList<Plugin<Pair<String, String>>>?
} else {
loaderFunctions = null
}
props.forEach { (k, v) -> run {
val key = k as String
val value = v as String
val member = extraProperties.find { it.name == key }
// if we have a property name in our object, we set it.
member?.setter?.call(kClass.objectInstance, value)
// if we have a property name in our PROJECT, we set it
// project functions that can be called for setting properties
val setterName = "set${key.replaceFirstChar { it.titlecaseChar() }}"
propertyFunctions.find { prop -> prop.name == setterName }?.call(project, value)
// assign this as an "extra property"
project.extensions.extraProperties.set(k, v)
// apply this property to whatever loader functions have been dynamically applied
val pair = Pair(key, value)
loaderFunctions?.forEach {
it.apply(pair)
}
}}
// assign target fields to our project (if our project has matching setters)
assignedExtraProperties.forEach { prop ->
val propertyName = prop.name
val setterName = "set${propertyName.replaceFirstChar { it.titlecaseChar() }}"
val projectMethod = propertyFunctions.find { it.name == setterName }
if (projectMethod != null) {
val getter = prop.getter
if (getter.property.isConst) {
projectMethod.call(project, getter.call())
} else {
projectMethod.call(project, getter.call(kClass.objectInstance))
}
}
}
}
}
/**
* Validates the minimum version of gradle supported
*/
fun minVersion(version: String) {
val compared = GradleVersion.current().compareTo(GradleVersion.version(version))
if (compared == -1) {
throw GradleException("This project requires Gradle $version or higher.")
}
}
/**
* Validates the maximum version of gradle supported
*/
fun maxVersion(version: String) {
val compared = GradleVersion.current().compareTo(GradleVersion.version(version))
if (compared == 1) {
throw GradleException("This project requires Gradle $version or lower.")
}
}
/**
* Gets all of the Maven-style Repository URLs for the specified project (or for the root project if not specified).
*
* @param project which project to get the repository root URLs for
* @param onlyRemote true to ONLY get the remote repositories (ie: don't include mavenLocal)
*/
fun getProjectRepositoryUrls(project: Project = this.project, onlyRemote: Boolean = true): List<String> {
val repositories = mutableListOf<String>()
val instance = project.repositories.filterIsInstance<org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository>()
@Suppress("DuplicatedCode")
instance.forEach { repo ->
val resolver = repo.createResolver()
if (resolver is org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver) {
// println("searching ${resolver.name}")
// println(resolver.root)
// all maven patterns are the same!
// https://plugins.gradle.org/m2/com/dorkbox/Utilities/maven-metadata.xml
// https://repo1.maven.org/maven2/com/dorkbox/Utilities/maven-metadata.xml
// https://repo.maven.apache.org/com/dorkbox/Utilities/maven-metadata.xml
if ((onlyRemote && !resolver.isLocal) || !onlyRemote) {
try {
val toURL = resolver.root.toASCIIString()
if (toURL.endsWith('/')) {
repositories.add(toURL)
} else {
// the root doesn't always end with a '/', and we must guarantee that
repositories.add("$toURL/")
}
} catch (ignored: Exception) {
}
}
}
}
return repositories
}
/**
* Gets all of the Maven-style Repository URLs for the specified project (or for the root project if not specified).
*
* @param project which project to get the repository root URLs for
* @param onlyRemote true to ONLY get the remote repositories (ie: don't include mavenLocal)
*/
fun getProjectBuildScriptRepositoryUrls(project: Project = this.project, onlyRemote: Boolean = true): List<String> {
val repositories = mutableListOf<String>()
val instance = project.buildscript.repositories.filterIsInstance<org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository>()
@Suppress("DuplicatedCode")
instance.forEach { repo ->
val resolver = repo.createResolver()
if (resolver is org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver) {
// println("searching ${resolver.name}")
// println(resolver.root)
// all maven patterns are the same!
// https://plugins.gradle.org/m2/com/dorkbox/Utilities/maven-metadata.xml
// https://repo1.maven.org/maven2/com/dorkbox/Utilities/maven-metadata.xml
// https://repo.maven.apache.org/com/dorkbox/Utilities/maven-metadata.xml
if ((onlyRemote && !resolver.isLocal) || !onlyRemote) {
try {
val toURL = resolver.root.toASCIIString()
if (toURL.endsWith('/')) {
repositories.add(toURL)
} else {
// the root doesn't always end with a '/', and we must guarantee that
repositories.add("$toURL/")
}
} catch (ignored: Exception) {
}
}
}
}
return repositories
}
/**
* Resolves all dependencies of the project buildscript
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveBuildScriptDependencies(project: Project = this.project): List<DependencyScanner.Maven> {
return project.buildscript.configurations.flatMap { config ->
config.resolvedConfiguration
.lenientConfiguration
.getFirstLevelModuleDependencies(Specs.SATISFIES_ALL)
.mapNotNull { dep ->
val module = dep.module.id
val group = module.group
val name = module.name
val version = module.version
DependencyScanner.Maven(group, name, version)
}
}
}
/**
* Resolves all *declared* dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveAllDeclaredDependencies(project: Project = this.project): List<DependencyScanner.DependencyData> {
// NOTE: we cannot createTree("compile") and createTree("runtime") using the same exitingNames and expect correct results.
// This is because a dependency might exist for compile and runtime, but have different children, therefore, the list
// will be incomplete
// there will be DUPLICATES! (we don't care about children or hierarchy, so we remove the dupes)
return (DependencyScanner.scan(project, "compileClasspath", false) +
DependencyScanner.scan(project, "runtimeClasspath", false)
).toSet().toList()
}
/**
* Recursively resolves all child dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveAllDependencies(project: Project = this.project): List<DependencyScanner.DependencyData> {
// NOTE: we cannot createTree("compile") and createTree("runtime") using the same exitingNames and expect correct results.
// This is because a dependency might exist for compile and runtime, but have different children, therefore, the list
// will be incomplete
// there will be DUPLICATES! (we don't care about children or hierarchy, so we remove the dupes)
return (DependencyScanner.scan(project, "compileClasspath") +
DependencyScanner.scan(project, "runtimeClasspath")
).toSet().toList()
}
/**
* Recursively resolves all child compile dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveCompileDependencies(project: Project = this.project): DependencyScanner.ProjectDependencies {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableMapOf<String, DependencyScanner.Dependency>()
DependencyScanner.createTree(project, "compileClasspath", projectDependencies, existingNames)
return DependencyScanner.ProjectDependencies(projectDependencies, existingNames.map { it.value })
}
/**
* Recursively resolves all child compile dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveRuntimeDependencies(project: Project = this.project): DependencyScanner.ProjectDependencies {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableMapOf<String, DependencyScanner.Dependency>()
DependencyScanner.createTree(project, "runtimeClasspath", projectDependencies, existingNames)
return DependencyScanner.ProjectDependencies(projectDependencies, existingNames.map { it.value })
}
/**
* set gradle project defaults, as used by dorkbox, llc
*/
fun defaults() {
addMavenRepositories()
fixMavenPaths()
defaultResolutionStrategy()
defaultCompileOptions()
fixIntellijPaths()
}
/**
* Adds maven-local + maven-central repositories
*/
fun addMavenRepositories() {
project.repositories.apply {
mavenLocal() // this must be first!
mavenCentral()
}
}
/**
* Change the source input from the opinionated sonatype paths to a simpler directory
*/
fun fixMavenPaths() {
// it is SUPER annoying to use the opinionated sonatype directory structure. I don't like it. We pass in the "configuration" action
// instead of doing it a different way, so the creation AND configuration of these sorucesets can occur. (Otherwise they won't)
val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer
val main = sourceSets.named("main", org.gradle.api.tasks.SourceSet::class.java).get()
val test = sourceSets.named("test", org.gradle.api.tasks.SourceSet::class.java).get()
main.apply {
java.apply {
setSrcDirs(project.files("src"))
include("**/*.java") // want to include java files for the source. 'setSrcDirs' resets includes...
}
if (hasKotlin) {
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("main").kotlin
kotlin.apply {
setSrcDirs(project.files("src"))
include("**/*.kt") // want to include kotlin files for the source. 'setSrcDirs' resets includes...
}
}
resources.setSrcDirs(project.files("resources"))
}
test.apply {
java.apply {
setSrcDirs(project.files("test"))
include("**/*.java") // want to include java files for the source. 'setSrcDirs' resets includes...
}
if (hasKotlin) {
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("test").kotlin
kotlin.apply {
setSrcDirs(project.files("test"))
include("**/*.kt") // want to include kotlin files for the source. 'setSrcDirs' resets includes...
}
}
resources.setSrcDirs(project.files("testResources"))
}
}
/**
* Fix the compiled output from intellij to be SEPARATE from gradle.
*/
fun fixIntellijPaths(location: String = "${project.buildDir}/classes-intellij") {
// put idea in its place! Not having this causes SO MANY PROBLEMS when building modules
// println("Setting intellij Compile location to: $location")
idea(project) {
// https://youtrack.jetbrains.com/issue/IDEA-175172
module {
// force the module to use OUR output dirs.
it.inheritOutputDirs = false
val mainDir = File(location)
it.outputDir = mainDir
it.testOutputDir = mainDir
}
}
// this has the side-effect of NOT creating the gradle directories....
// also... if we CLEAN the project, the STANDARD build dir is DELETED. This messes up mixed kotlin + java projects, because the
// INTELLIJ compiler will compile the GRADLE classes (not the intellij classes) to the WRONG location!
// This appears to be triggered by a file watcher on the build dir.
// A subsequent problem created by this, is when we go to compile an archive (jar/etc) NORMALLY (via gradle)... there will be duplicates!
val hasClean = project.gradle.startParameter.taskNames.filter { taskName ->
taskName.lowercase(Locale.getDefault()).contains("clean")
}
if (hasClean.isNotEmpty()) {
val task = project.tasks.last { task -> task.name == hasClean.last() }
task.doLast {
File(location).deleteRecursively()
createBuildDirs(project)
}
}
else {
// make sure that the source set directories all exist. THIS SHOULD NOT BE A PROBLEM! (but it is)
project.afterEvaluate { prj ->
createBuildDirs(prj)
}
}
}
private fun createBuildDirs(prj: Project) {
val sourceSets = prj.extensions.getByName("sourceSets") as SourceSetContainer
sourceSets.forEach { set ->
set.output.classesDirs.forEach { dir ->
// println("Creating dir: ${dir.absolutePath}")
if (!dir.exists()) {
dir.mkdirs()
}
}
}
}
/**
* Configure a default resolution strategy. While not necessary, this is used for enforcing sane project builds
*/
fun defaultResolutionStrategy() {
project.configurations.forEach { config ->
config.resolutionStrategy {
it.apply {
// fail eagerly on version conflict (includes transitive dependencies)
// e.g. multiple different versions of the same dependency (group and name are equal)
failOnVersionConflict()
// if there is a version we specified, USE THAT VERSION (over transitive versions)
preferProjectModules()
// cache dynamic versions for 10 minutes
cacheDynamicVersionsFor(10 * 60, "seconds")
// don't cache changing modules at all
cacheChangingModulesFor(0, "seconds")
}
}
}
}
/**
* Always compile java with UTF-8, make it incremental, and compile `package-info.java` classes
*/
fun defaultCompileOptions() {
project.tasks.withType(JavaCompile::class.java, object: Action<Task> {
override fun execute(task: Task) {
task as JavaCompile
task.options.encoding = "UTF-8"
task.options.isIncremental = true
task.options.compilerArgs.add("-Xpkginfo:always")
}
})
}
/**
* Basic, default compile configurations
*/
fun compileConfiguration(javaVersion: JavaVersion,
kotlinJavaVersion: JavaVersion = javaVersion,
kotlinActions: KotlinJvmOptions.() -> Unit = {}) {
val javaVer = javaVersion.toString()
val kotlinJavaVer = kotlinJavaVersion.toString().also {
if (it.startsWith("1.")) {
if (it == "1.6" || it == "1.8") {
it
} else {
it.substring(2)
}
} else {
it
}
}
val kotlinVer: String = try {
if (hasKotlin) {
val version = project.getKotlinPluginVersion()
// we ONLY care about the major.minor
val secondDot = version.indexOf('.', version.indexOf('.')+1)
version.substring(0, secondDot)
} else {
defaultKotlinVersion
}
} catch (e: Exception) {
// in case we cannot parse it from the plugin, provide a reasonable default (the latest stable)
defaultKotlinVersion
}
// NOTE: these must be anonymous inner classes because gradle cannot handle this in kotlin 1.5
project.tasks.withType(JavaCompile::class.java, object: Action<Task> {
override fun execute(task: Task) {
task as JavaCompile
task.doFirst(object: Action<Task> {
override fun execute(it: Task) {
it as JavaCompile
println("\tCompiling classes to Java ${JavaVersion.toVersion(it.targetCompatibility)}")
}
})
task.options.encoding = "UTF-8"
// -Xlint:deprecation
task.options.isDeprecation = true
// -Xlint:unchecked
task.options.compilerArgs.add("-Xlint:unchecked")
task.sourceCompatibility = javaVer
task.targetCompatibility = javaVer
}
})
// NOTE: these must be anonymous inner classes because gradle cannot handle this in kotlin 1.5
project.tasks.withType(org.gradle.jvm.tasks.Jar::class.java, object: Action<Task> {
override fun execute(task: Task) {
task as Jar
task.duplicatesStrategy = DuplicatesStrategy.FAIL
task.doLast(object: Action<Task> {
override fun execute(task: Task) {
task as Jar
if (task.didWork) {
val file = task.archiveFile.get().asFile
println("\t${file.path}\n\tSize: ${file.length().toDouble() / (1_000 * 1_000)} MB")
}
}
})
}
})
if (hasKotlin) {
// NOTE: these must be anonymous inner classes because gradle cannot handle this in kotlin 1.5
project.tasks.withType(KotlinCompile::class.java, object: Action<Task> {
override fun execute(task: Task) {
task as KotlinCompile
task.doFirst(object: Action<Task> {
override fun execute(it: Task) {
it as KotlinCompile
println("\tCompiling classes to Kotlin ${it.kotlinOptions.languageVersion}, Java ${it.kotlinOptions.jvmTarget}")
}
})
task.kotlinOptions.jvmTarget = kotlinJavaVer
// default is whatever the version is that we are running, or XXXXX if we cannot figure it out
task.kotlinOptions.apiVersion = kotlinVer
task.kotlinOptions.languageVersion = kotlinVer
// see: https://kotlinlang.org/docs/reference/using-gradle.html
kotlinActions(task.kotlinOptions)
}
})
}
// also have to tell intellij (if present) to behave.
idea(project) {
module {
it.jdkName = javaVer
// by default, we ALWAYS want sources. If you have sources, you don't need javadoc (since the sources have them in it already)
it.isDownloadJavadoc = false
it.isDownloadSources = true
}
}
}
/**
* Get the SWT maven ID based on the os/arch. ALSO fix SWT maven configuration IDs
*
* This is spectacularly frustrating because there aren't "normal" releases of SWT.
*/
fun getSwtMavenId(version: String): String {
// SEE: https://repo1.maven.org/maven2/org/eclipse/platform/
// windows
// org.eclipse.swt.win32.win32.x86
// org.eclipse.swt.win32.win32.x86_64
// linux
// org.eclipse.swt.gtk.linux.x86
// org.eclipse.swt.gtk.linux.x86_64
// macos
// org.eclipse.swt.cocoa.macosx.x86_64
val currentOS = org.gradle.internal.os.OperatingSystem.current()
val swtType = if (System.getProperty("os.arch").matches(".*64.*".toRegex())) {
when {
currentOS.isWindows -> SwtType.WIN_64
currentOS.isMacOsX -> SwtType.MAC_64
else -> SwtType.LINUX_64
}
} else {
when {
currentOS.isWindows -> SwtType.WIN_32
currentOS.isMacOsX -> SwtType.MAC_64 // not possible on mac, but here for completeness
else -> SwtType.LINUX_32
}
}
val fullId = swtType.fullId(version)
if (!fixedSWT) {
fixedSWT = true
project.configurations.all { config ->
config.resolutionStrategy { strategy ->
strategy.dependencySubstitution { sub ->
// The maven property ${osgi.platform} is not handled by Gradle for the SWT builds
// so we replace the dependency, using the osgi platform from the project settings
sub.substitute(sub.module("org.eclipse.platform:org.eclipse.swt.\${osgi.platform}"))
.using(sub.module(fullId))
}
}
}
}
return fullId
}
/**
* Load JPMS for a specific java version using the default configuration
*/
fun jpms(javaVersion: JavaVersion): JavaXConfiguration {
return JavaXConfiguration(javaVersion, project, this)
}
/**
* Load and configure JPMS for a specific java version
*/
fun jpms(javaVersion: JavaVersion, block: SourceSetContainer2.() -> Unit): JavaXConfiguration {
val javaX = JavaXConfiguration(javaVersion, project, this)
block(SourceSetContainer2(javaX))
return javaX
}
/**
* Fix issues where code (usually test code) needs access to **INTERNAL** scope objects.
* -- at the moment, this only fixes gradle -- not intellij
* There are also gradle 8 warnings when using this.
*
* https://stackoverflow.com/questions/59072889/how-to-test-kotlin-function-declared-internal-from-within-tests-when-java-test
* https://youtrack.jetbrains.com/issue/KT-20760
* https://youtrack.jetbrains.com/issue/KT-45787
* https://stackoverflow.com/questions/57050889/kotlin-internal-members-not-accessible-from-alternative-test-source-set-in-gradl
* https://youtrack.jetbrains.com/issue/KT-34901
*
* (related)
* https://github.com/JetBrains/kotlin/blob/master/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt
* https://github.com/bazelbuild/rules_kotlin/pull/465
* https://github.com/bazelbuild/rules_kotlin/issues/211
*
* Two things are required for this to work
*
* 1) The kotlin module names must be the same
* 2) The kotlin modules must be associated
*/
fun allowKotlinInternalAccessForTests(moduleName: String, vararg accessGroup: AccessGroup) {
if (hasKotlin) {
// Make sure to cleanup the any possible license file on clean
println("\tAllowing kotlin internal access for $moduleName")
// NOTE: these must be anonymous inner classes because gradle cannot handle this in kotlin 1.5
project.tasks.withType(KotlinCompile::class.java, object: Action<Task> {
override fun execute(task: Task) {
task as KotlinCompile
// must be the same module name as the regular one (which is the project name). If it is a different name, it crashes at runtime
task.kotlinOptions.moduleName = moduleName
}
})
accessGroup.forEach {
// allow code in a *different* directory access to "internal" scope members of code.
// THIS FIXES GRADLE - BUT NOT INTELLIJ!
val kotlinExt = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java)
kotlinExt.target.compilations.getByName(it.sourceName).apply {
it.targetNames.forEach { targetName ->
associateWith(target.compilations.getByName(targetName))
}
}
}
}
}
class AccessGroup(val sourceName: String, vararg val targetNames: String)
// https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html
// Every implementation of the Java platform is required to support the following standard MessageDigest algorithms:
// MD5
// SHA-1
// SHA-256
fun md5(file: File?): ByteArray {
if (file == null || !file.canRead()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("MD5")
file.forEachBlock { bytes: ByteArray, bytesRead: Int ->
md.update(bytes, 0, bytesRead)
}
return md.digest()
}
fun md5(text: String?): ByteArray {
if (text.isNullOrEmpty()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("MD5")
val bytes = text.toByteArray(Charsets.UTF_8)
md.update(bytes, 0, bytes.size)
return md.digest()
}
fun sha1(file: File?): ByteArray {
if (file == null || !file.canRead()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("SHA-1")
file.forEachBlock { bytes: ByteArray, bytesRead: Int ->
md.update(bytes, 0, bytesRead)
}
return md.digest()
}
fun sha1(text: String?): ByteArray {
if (text.isNullOrEmpty()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("SHA-1")
val bytes = text.toByteArray(Charsets.UTF_8)
md.update(bytes, 0, bytes.size)
return md.digest()
}
fun sha256(file: File?): ByteArray {
if (file == null || !file.canRead()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("SHA-256")
file.forEachBlock { bytes: ByteArray, bytesRead: Int ->
md.update(bytes, 0, bytesRead)
}
return md.digest()
}
fun sha256(text: String?): ByteArray {
if (text.isNullOrEmpty()) {
return ByteArray(0)
}
val md = MessageDigest.getInstance("SHA-256")
val bytes = text.toByteArray(Charsets.UTF_8)
md.update(bytes, 0, bytes.size)
return md.digest()
}
}