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>() val assignedExtraProperties = kClass.declaredMemberProperties.filterIsInstance>() // 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>>? if (project.extensions.extraProperties.has("property_loader_functions")) { @Suppress("UNCHECKED_CAST") loaderFunctions = project.extensions.extraProperties["property_loader_functions"] as ArrayList>>? } 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.") } } /** * Resolves all child dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ fun resolveDependencies(): List { val projectDependencies = mutableListOf() val existingNames = mutableSetOf() DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) return projectDependencies } /** * Resolves all child compile dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ fun resolveCompileDependencies(): List { val projectDependencies = mutableListOf() val existingNames = mutableSetOf() DependencyScanner.scan(project, "compileClasspath", projectDependencies, existingNames) return projectDependencies } /** * Resolves all child compile dependencies of the project * * THIS MUST BE IN "afterEvaluate" or run from a specific task. */ fun resolveRuntimeDependencies(): List { val projectDependencies = mutableListOf() val existingNames = mutableSetOf() DependencyScanner.scan(project, "runtimeClasspath", projectDependencies, existingNames) return projectDependencies } /** * 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 } } // 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") } } } } } /** * 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" } 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) } } }