Enable UI Designer instrumentation in Gradle build
This commit is contained in:
@@ -0,0 +1,170 @@
|
|||||||
|
@file:Suppress("unused") // usages in build scripts are not tracked properly
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.FileCollection
|
||||||
|
import org.gradle.api.internal.ConventionTask
|
||||||
|
import org.gradle.api.plugins.ExtensionAware
|
||||||
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
|
import org.gradle.api.tasks.*
|
||||||
|
import org.gradle.api.tasks.compile.AbstractCompile
|
||||||
|
import org.gradle.kotlin.dsl.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun Project.configureInstrumentation() {
|
||||||
|
plugins.matching { it::class.java.canonicalName.startsWith("org.jetbrains.kotlin.gradle.plugin") }.all {
|
||||||
|
// When we change the output classes directory, Gradle will automatically configure
|
||||||
|
// the test compile tasks to use the instrumented classes. Normally this is fine,
|
||||||
|
// however, it causes problems for Kotlin projects:
|
||||||
|
|
||||||
|
// The "internal" modifier can be used to restrict access to the same module.
|
||||||
|
// To make it possible to use internal methods from the main source set in test classes,
|
||||||
|
// the Kotlin Gradle plugin adds the original output directory of the Java task
|
||||||
|
// as "friendly directory" which makes it possible to access internal members
|
||||||
|
// of the main module. Also this directory should be available on classpath during compilation
|
||||||
|
|
||||||
|
// This fails when we change the classes dir. The easiest fix is to prepend the
|
||||||
|
// classes from the "friendly directory" to the compile classpath.
|
||||||
|
val testCompile = tasks.findByName("compileTestKotlin") as AbstractCompile?
|
||||||
|
testCompile?.doFirst {
|
||||||
|
val mainSourceSet = the<JavaPluginConvention>().sourceSets.getByName("main")
|
||||||
|
testCompile.classpath = (testCompile.classpath
|
||||||
|
- mainSourceSet.output.classesDirs
|
||||||
|
+ files((mainSourceSet as ExtensionAware).extra.get("classesDirsCopy")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
the<JavaPluginConvention>().sourceSets.all { sourceSetParam ->
|
||||||
|
// This copy will ignore filters, but they are unlikely to be used.
|
||||||
|
val classesDirs = (sourceSetParam.output.classesDirs as ConfigurableFileCollection).from as Collection<Any>
|
||||||
|
|
||||||
|
val classesDirsCopy = project.files(classesDirs.toTypedArray()).filter { it.exists() }
|
||||||
|
(sourceSetParam as ExtensionAware).extra.set("classesDirsCopy", classesDirsCopy)
|
||||||
|
|
||||||
|
logger.info("Saving old sources dir for project ${project.name}")
|
||||||
|
val instrumentedClassesDir = File(project.buildDir, "classes/${sourceSetParam.name}-instrumented")
|
||||||
|
(sourceSetParam.output.classesDirs as ConfigurableFileCollection).setFrom(instrumentedClassesDir)
|
||||||
|
val instrumentTask = project.tasks.create(sourceSetParam.getTaskName("instrument", "classes"), IntelliJInstrumentCodeTask::class.java)
|
||||||
|
instrumentTask.apply {
|
||||||
|
dependsOn(sourceSetParam.classesTaskName).onlyIf { !classesDirsCopy.isEmpty }
|
||||||
|
sourceSet = sourceSetParam
|
||||||
|
originalClassesDirs = classesDirsCopy
|
||||||
|
output = instrumentedClassesDir
|
||||||
|
}
|
||||||
|
|
||||||
|
instrumentTask.outputs.dir(instrumentedClassesDir)
|
||||||
|
// Ensure that our task is invoked when the source set is built
|
||||||
|
sourceSetParam.compiledBy(instrumentTask)
|
||||||
|
@Suppress("UNUSED_EXPRESSION")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheableTask
|
||||||
|
open class IntelliJInstrumentCodeTask : ConventionTask() {
|
||||||
|
companion object {
|
||||||
|
private const val FILTER_ANNOTATION_REGEXP_CLASS = "com.intellij.ant.ClassFilterAnnotationRegexp"
|
||||||
|
private const val LOADER_REF = "java2.loader"
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceSet: SourceSet? = null
|
||||||
|
|
||||||
|
@Input
|
||||||
|
var originalClassesDirs: FileCollection? = null
|
||||||
|
|
||||||
|
@get:InputFiles
|
||||||
|
val sourceDirs: FileCollection
|
||||||
|
get() = project.files(sourceSet!!.allSource.srcDirs.filter { !sourceSet!!.resources.contains(it) && it.exists() })
|
||||||
|
|
||||||
|
@get:OutputDirectory
|
||||||
|
var output: File? = null
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun instrumentClasses() {
|
||||||
|
logger.info("input files are: ${originalClassesDirs?.joinToString("; ", transform = { "'${it.name}'${if (it.exists()) "" else " (does not exists)" }"})}")
|
||||||
|
copyOriginalClasses()
|
||||||
|
|
||||||
|
val classpath = project.ideaSdkDeps("javac2.jar", "jdom.jar", "asm-all.jar", "jgoodies-forms.jar")
|
||||||
|
|
||||||
|
ant.withGroovyBuilder {
|
||||||
|
"taskdef"("name" to "instrumentIdeaExtensions",
|
||||||
|
"classpath" to classpath.asPath,
|
||||||
|
"loaderref" to LOADER_REF,
|
||||||
|
"classname" to "com.intellij.ant.InstrumentIdeaExtensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Compiling forms and instrumenting code with nullability preconditions")
|
||||||
|
val instrumentNotNull = prepareNotNullInstrumenting(classpath)
|
||||||
|
instrumentCode(sourceDirs, instrumentNotNull)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyOriginalClasses() {
|
||||||
|
project.copy {
|
||||||
|
from(originalClassesDirs)
|
||||||
|
into(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareNotNullInstrumenting(classpath: ConfigurableFileCollection): Boolean {
|
||||||
|
ant.withGroovyBuilder {
|
||||||
|
"typedef"("name" to "skip",
|
||||||
|
"classpath" to classpath.asPath,
|
||||||
|
"loaderref" to LOADER_REF,
|
||||||
|
"classname" to FILTER_ANNOTATION_REGEXP_CLASS)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun instrumentCode(srcDirs: FileCollection, instrumentNotNull: Boolean) {
|
||||||
|
val headlessOldValue = System.setProperty("java.awt.headless", "true")
|
||||||
|
|
||||||
|
// Instrumentation needs to have access to sources of forms for inclusion
|
||||||
|
val depSourceDirectorySets = project.configurations["compile"].dependencies.withType(ProjectDependency::class.java)
|
||||||
|
.map { p -> p.dependencyProject.the<JavaPluginConvention>().sourceSets.getByName("main").allSource.sourceDirectories }
|
||||||
|
val instrumentationClasspath =
|
||||||
|
depSourceDirectorySets.fold(sourceSet!!.compileClasspath) { acc, v -> acc + v }.asPath.also {
|
||||||
|
logger.info("Using following dependency source dirs: $it")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Running instrumentIdeaExtensions with srcdir=${srcDirs.asPath}}, destdir=$output and classpath=$instrumentationClasspath")
|
||||||
|
|
||||||
|
ant.withGroovyBuilder {
|
||||||
|
"instrumentIdeaExtensions"("srcdir" to srcDirs.asPath,
|
||||||
|
"destdir" to output,
|
||||||
|
"classpath" to instrumentationClasspath,
|
||||||
|
"includeantruntime" to false,
|
||||||
|
"instrumentNotNull" to instrumentNotNull) {
|
||||||
|
if (instrumentNotNull) {
|
||||||
|
ant.withGroovyBuilder {
|
||||||
|
"skip"("pattern" to "kotlin/Metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headlessOldValue != null) {
|
||||||
|
System.setProperty("java.awt.headless", headlessOldValue)
|
||||||
|
} else {
|
||||||
|
System.clearProperty("java.awt.headless")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,4 +109,5 @@ projectTest {
|
|||||||
testsJar {}
|
testsJar {}
|
||||||
|
|
||||||
classesDirsArtifact()
|
classesDirsArtifact()
|
||||||
|
configureInstrumentation()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ apply { plugin("kotlin") }
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
compileOnly(ideaSdkDeps("openapi", "idea", "external-system-rt"))
|
compileOnly(ideaSdkDeps("openapi", "idea", "external-system-rt", "forms_rt"))
|
||||||
compileOnly(ideaPluginDeps("gradle-tooling-api", "gradle", "gradle-base-services", plugin = "gradle"))
|
compileOnly(ideaPluginDeps("gradle-tooling-api", "gradle", "gradle-base-services", plugin = "gradle"))
|
||||||
compileOnly(ideaPluginDeps("Groovy", plugin = "Groovy"))
|
compileOnly(ideaPluginDeps("Groovy", plugin = "Groovy"))
|
||||||
|
|
||||||
@@ -55,4 +55,6 @@ testsJar()
|
|||||||
|
|
||||||
projectTest {
|
projectTest {
|
||||||
workingDir = rootDir
|
workingDir = rootDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureInstrumentation()
|
||||||
|
|||||||
Reference in New Issue
Block a user