GradleUtils/src/dorkbox/gradle/StaticMethodsAndTools.kt

376 lines
15 KiB
Kotlin
Raw Normal View History

package dorkbox.gradle
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.util.*
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.declaredMemberProperties
open class StaticMethodsAndTools(private val project: Project) {
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
/**
* 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 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.capitalize()}"
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 {
val propertyName = it.name
val setterName = "set${propertyName.capitalize()}"
val projectMethod = propertyFunctions.find { prop -> prop.name == setterName }
if (projectMethod != null) {
if (it.getter.property.isConst) {
projectMethod.call(project, it.getter.call())
} else {
projectMethod.call(project, it.getter.call(kClass.objectInstance))
}
}
}
}
}
/**
* Validates the minimum version of gradle supported
*/
fun minVersion(version: String) {
val compared = org.gradle.util.GradleVersion.current().compareTo(org.gradle.util.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 = org.gradle.util.GradleVersion.current().compareTo(org.gradle.util.GradleVersion.version(version))
if (compared == 1) {
throw GradleException("This project requires Gradle $version or lower.")
}
}
2020-05-07 19:06:16 +02:00
/**
* Resolves all child dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
2020-05-07 19:06:16 +02:00
*/
fun resolveDependencies(): List<DependencyScanner.Dependency> {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableSetOf<String>()
2020-05-07 19:06:16 +02:00
DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames)
DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames)
2020-05-07 19:06:16 +02:00
return projectDependencies
2020-05-07 19:06:16 +02:00
}
/**
* Resolves all child compile dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
2020-05-07 19:06:16 +02:00
*/
fun resolveCompileDependencies(): List<DependencyScanner.Dependency> {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableSetOf<String>()
2020-05-07 19:06:16 +02:00
DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames)
2020-05-07 19:06:16 +02:00
return projectDependencies
}
2020-05-07 19:06:16 +02:00
/**
* Resolves all child compile dependencies of the project
*
* THIS MUST BE IN "afterEvaluate" or run from a specific task.
*/
fun resolveRuntimeDependencies(): List<DependencyScanner.Dependency> {
val projectDependencies = mutableListOf<DependencyScanner.Dependency>()
val existingNames = mutableSetOf<String>()
2020-05-07 19:06:16 +02:00
DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames)
return projectDependencies
2020-05-07 19:06:16 +02:00
}
2020-06-05 19:35:50 +02:00
/**
* Fix the compiled output from intellij to be SEPARATE from gradle.
*/
fun fixIntellijPaths(location: String = "${project.buildDir}/classes-intellij") {
project.allprojects.forEach { project ->
// https://discuss.gradle.org/t/can-a-plugin-itself-add-buildscript-dependencies-and-then-apply-a-plugin/25039/4
apply(project, "idea")
// put idea in it's place! Not having this causes SO MANY PROBLEMS when building modules
idea(project) {
// https://youtrack.jetbrains.com/issue/IDEA-175172
module {
val mainDir = File(location)
it.outputDir = mainDir
it.testOutputDir = mainDir
// 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
}
2020-06-05 19:35:50 +02:00
}
// this has the side-effect of NOT creating the gradle directories....
// make sure that the source set directories all exist. THIS SHOULD NOT BE A PROBLEM!
project.afterEvaluate { prj ->
prj.allprojects.forEach { proj ->
val javaPlugin: JavaPluginConvention = proj.convention.getPlugin(JavaPluginConvention::class.java)
val sourceSets = javaPlugin.sourceSets
sourceSets.forEach { set ->
set.output.classesDirs.forEach { dir ->
if (!dir.exists()) {
dir.mkdirs()
}
}
}
}
}
}
}
/**
* Configure a default resolution strategy. While not necessary, this is used for enforcing sane project builds
*/
fun defaultResolutionStrategy() {
project.allprojects.forEach { project ->
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")
}
}
}
}
2020-06-05 19:35:50 +02:00
}
/**
* Basic, default compile configurations
*/
fun compileConfiguration(javaVersion: JavaVersion, kotlinActions: (KotlinJvmOptions) -> Unit = {}) {
val javaVer = javaVersion.toString()
val kotlinVer: String = try {
val kot = project.plugins.findPlugin("org.jetbrains.kotlin.jvm") as org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper?
val version = kot?.kotlinPluginVersion ?: "1.3.0"
// we ONLY care about the major.minor
val secondDot = version.indexOf('.', version.indexOf('.')+1)
version.substring(0, secondDot)
} catch (e: Exception) {
// in case we cannot parse it from the plugin, provide a reasonable default
"1.3"
}
project.allprojects.forEach { project ->
project.tasks.withType(JavaCompile::class.java) { task ->
task.doFirst {
println("\tCompiling classes to Java $javaVersion")
}
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
}
project.tasks.withType(Jar::class.java) {
it.duplicatesStrategy = DuplicatesStrategy.FAIL
}
project.tasks.withType(KotlinCompile::class.java) { task ->
task.doFirst {
println("\tCompiling classes to Kotlin ${task.kotlinOptions.languageVersion}, Java ${task.kotlinOptions.jvmTarget}")
}
task.sourceCompatibility = javaVer
task.targetCompatibility = javaVer
task.kotlinOptions.jvmTarget = javaVer
// default is whatever the version is that we are running, or 1.3 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)
}
}
}
/**
* 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
// macoxs
// org.eclipse.swt.cocoa.macosx.x86_64
val currentOS = org.gradle.internal.os.OperatingSystem.current()
val windowingTk = when {
currentOS.isWindows -> "win32"
currentOS.isMacOsX -> "cocoa"
else -> "gtk"
}
val platform = when {
currentOS.isWindows -> "win32"
currentOS.isMacOsX -> "macosx"
else -> "linux"
}
var arch = System.getProperty("os.arch")
arch = when {
arch.matches(".*64.*".toRegex()) -> "x86_64"
else -> "x86"
}
val mavenId = "$windowingTk.$platform.$arch"
if (!fixedSWT) {
fixedSWT = true
project.allprojects.forEach { project ->
project.configurations.all { config ->
config.resolutionStrategy { strat ->
strat.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}"))
.with(sub.module("org.eclipse.platform:org.eclipse.swt.$mavenId:$version"))
}
}
}
}
}
return "org.eclipse.platform:org.eclipse.swt.$mavenId:$version"
}
2020-06-05 19:35:50 +02:00
private 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
private 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)
}
}
}