Generated java source code not found by kotlin compiler - java

I'm writing a gradle plugin to generate Java code. My plugin generates code that's based on the Android R class, so it's dependent on the
processVariantResources
Task. In my plugin, I'm using this code to do that:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyPluginExtension>(PLUGIN_NAME, MyPluginExtension::class.java)
project.tasks.whenTaskAdded { task ->
if (task.name.startsWith("process") && task.name.contains("Resources") && !task.name.contains("Test")) {
val index = task.name.indexOf("Resources")
val variantName = task.name.substring(7, index)
//Create my task and add it to the project, make it dependent on processResources
project.task("my${variantName}ResourceTask") {
it.doLast {
generateSomeCodeForVariant(project, extension, variantName)
}
taskList.add(it)
}.dependsOn(task)
}
}
project.afterEvaluate {
val appExtension = project.extensions.findByType(AppExtension::class.java)
appExtension?.applicationVariants?.all { variant ->
val myTask = project.tasks.getByName("my${variant.name.capitalize()}ResourcesTask")
val outputDir = "${project.buildDir}/generated/source/myplugin/${variant.name}"
//register my task as java generating
variant.registerJavaGeneratingTask(myTask, File(outputDir))
}
}
}
then, in the build.gradle of the project where I'm using this plugin, I've added
android {
sourceSets {
main {
java.srcDirs += ['build/generated/source/myplugin']
kotlin.srcDirs += ['build/generated/source/myplugin']
}
}
}
My plugin actually generates source code to the directory:
build/generated/source/myplugin/com/mygroup/myartifact
Anyway, the code gets generated correctly, and put in the correct place, but I can't get the compiler to recognize my generated code. Any help would be appreciated.

I refactored the code and implemented the strategy describe here:
my new code looks like this:
project.afterEvaluate {
val appExtension = project.extensions.findByType(AppExtension::class.java)
appExtension?.applicationVariants?.all { variant ->
val processResourcesTask = project.tasks.getByName("process${variant.name.capitalize()}Resources")
val myTask = it.task("my${variant.name.capitalize()}ResourceTask") {
it.doLast {
doSomeStuff(project, extension, variant.name.capitalize())
}
}.dependsOn(processResourcesTask)
val outputDir = "${project.buildDir}/generated/source/myplugin/${variant.name}"
variant.registerJavaGeneratingTask(myTask, File(outputDir))
val kotlinCompileTask = it.tasks.findByName("compile${variant.name.capitalize()}Kotlin") as? SourceTask
if (kotlinCompileTask != null) {
kotlinCompileTask.dependsOn(myTask)
val srcSet = it.objects.sourceDirectorySet("myplugin", "myplugin").srcDir(outputDir)
kotlinCompileTask.source(srcSet)
}
}
}
I also removed the sourceSets definition from the client build.gradle file, and
now everything works correctly.

Related

How to publish custom artifacts with `com.gradle.plugin-publish` plugin?

I'm trying to use ProGuard to minimize my gradle plugin and it works well, producing the output file a-min.jar
However, com.gradle.plugin-publish plugin just doesn't recognize it when using task publishPlugins, it tells
Cannot determine main artifact to upload - could not find jar artifact with empty classifier
Strangely, when using publishToMavenLocal it did work well.
I've tried some tricks to replace the main jar with my processed jar, but always failed. Is there any new sight here?
afterEvaluate {
publishing {
publications {
withType<MavenPublication> {
if (name == "pluginMaven") {
setArtifacts(listOf(
artifact(minJarPath){
classifier = ""
extension = "jar"
}
))
}
}
}
}
}
I've tried to decompile com.gradle.plugin-publish:0.18.0, getting that
private File findMainArtifact() {
if (this.useAutomatedPublishing) {
for (UsageContext variant : this.javaRuntimeVariants) {
for (PublishArtifact artifact : variant.getArtifacts()) {
if (Util.isBlank(artifact.getClassifier()) && "jar".equals(artifact.getExtension()))
return artifact.getFile();
}
}
} else {
Configuration archivesConfiguration = getProject().getConfigurations().getByName("archives");
for (PublishArtifact artifact : archivesConfiguration.getAllArtifacts()) {
if (Util.isBlank(artifact.getClassifier()) && "jar".equals(artifact.getExtension()))
return artifact.getFile();
}
}
throw new IllegalArgumentException("Cannot determine main artifact to upload - could not find jar artifact with empty classifier");
}
But I don't know how to add a artifact to "AllArtifacts".
Full code: GitHub#ArcticLampyrid/gradle-git-version
Resolved by some tricks although it is not elegant.
Solution
Create a real jar task to include proguard outputs and add it to outgoing of apiElements & runtimeElements.
Delete the unprocessed jar from apiElements & runtimeElements. (for publishPlugins task)
Also, modify publications (for publishToMavenLocal task)
Example
/* Written in Kotlin DSL for Gradle */
/* build.gradle.kts */
// for `publishPlugins` task
val minJar by tasks.creating(Jar::class){
dependsOn(genMinJar)
from(zipTree(minJarPath))
}
jar {
archiveClassifier.set("unpacked")
}
configurations {
artifacts {
arrayOf(apiElements, runtimeElements).forEach {
it.get().outgoing.artifacts.removeIf { it.classifier == "unpacked" }
it.get().outgoing.artifact(minJar)
}
}
}
// for `publishToMavenLocal` task
afterEvaluate {
publishing {
publications {
withType<MavenPublication> {
if (name == "pluginMaven") {
setArtifacts(listOf(minJar))
}
}
}
}
}
Details
See https://github.com/ArcticLampyrid/gradle-git-version/commit/23ccfc8bdf16c9352024dc0b7722f534a310551f

replace tokens in resource and webapp files

I would like to improve the way our gradle build replaces #VERSION# with project.version in some of our resource and webapp files.
I currently (groovy) have:
def replaceVersionInResources(pattern) {
processResources {
filesMatching(pattern) {
it.filter(ReplaceTokens, tokens: ['VERSION': project.version])
}
}
}
which is imported in our build.gradle files and then called with:
replaceVersionInResources('**/*.conf')
Note that the project.version must be set before calling replaceVersionInResources.
The improved version should retrieve the file-pattern from an extension property and allow the
project.version to be set at a later point.
I was able to achieve this for Java source code and am looking for a similar solution for resources and webapp files.
The current webapp code is very similar (obviously with the same downsides):
def replaceVersionInWebapp(matchingPattern) {
if (project.tasks.findByName('bootWar')) {
bootWar {
filesMatching(matchingPattern) {
it.filter(ReplaceTokens, tokens: ['VERSION': project.version])
}
}
}
}
Here is our solution for Java Source Code.
Feedback is welcome!
I created a Sync task, which used the main SourceSet as input:
from(project.the<SourceSetContainer>()[SourceSet.MAIN_SOURCE_SET_NAME].java)
and then afterwards wired the output of this task as input to the CompileJava tasks:
tasks.withType<JavaCompile>().configureEach {
setSource(replaceVersionInSource.get().outputs)
}
Everything below is from one file (buildSrc/src/main/kotlin/versioning.gradle.kts):
// Based on https://discuss.gradle.org/t/indirection-lazy-evaluation-for-copy-spec-filter-properties/7173/7
class DeferredReplaceTokens(`in`: Reader) : FilterReader(`in`) {
private var actualReader: FilterReader? = null
var tokens: MapProperty<String, String>? = null
private fun reader(): FilterReader {
actualReader = actualReader ?: ReplaceTokens(this.`in`).also { replaceTokens ->
val tokens = this.tokens
if (tokens == null) {
println("No tokens set. tokens is null")
} else {
tokens.keySet().get()
.map { key -> key to tokens.getting(key).get() }
.onEach { println("Token: ${it.first} : ${it.second}") }
.map { (key, value) -> ReplaceTokens.Token().also { it.key = key; it.value = value } }
.forEach(replaceTokens::addConfiguredToken)
}
}
return actualReader!!
}
override fun read(cbuf: CharArray, off: Int, len: Int): Int = reader().read(cbuf, off, len)
override fun read(): Int = reader().read()
override fun close() = reader().close()
}
Here is the extension I've created, with the tokens-map and the resourcePattern.
open class ReplaceVersionExtension #Inject constructor(
project: Project
) {
val tokens: MapProperty<String, String> = project.objects.mapProperty(String::class.java, String::class.java)
.convention(project.provider { mutableMapOf("VERSION" to "${project.version}") })
val resourcePattern: Property<String> = project.objects.property(String::class.java)
val webappPattern: Property<String> = project.objects.property(String::class.java)
}
val replaceVersion = extensions.create<ReplaceVersionExtension>("replaceVersion")
val replaceVersionInSource by tasks.registering(Sync::class) {
onlyIf { replaceVersion.tokens.get().isNotEmpty() }
from(project.the<SourceSetContainer>()[SourceSet.MAIN_SOURCE_SET_NAME].java)
inputs.property("tokens", replaceVersion.tokens)
filter<DeferredReplaceTokens>("tokens" to replaceVersion.tokens)
into(layout.buildDirectory.dir("src"))
}
Here is the part where I tell the JavaCompile tasks to use the output from the Sync task above.
tasks.withType<JavaCompile>().configureEach {
setSource(replaceVersionInSource.get().outputs)
}
This code is then imported and if necessary configured using the extension:
replaceVersion {
tokens.put("VERSION", "CUSTOM_VERSION_TEXT")
tokens.put("RELEASE", "2021")
}

Gradle - throw exception if project still has SNAPSHOT dependencies

I want to fail the gradle build if the current project still has snapshot dependencies.
My code so far only looks for java dependencies, missing the .NET ones so it only works for java projects. I want to make it work for all projects.
def addSnapshotCheckingTask(Project project) {
project.tasks.withType(JavaCompile) { compileJava ->
project.tasks.create(compileJava.name + 'SnapshotChecking', {
onlyIf {
project.ext.isRelease || project.ext.commitVersion != null
}
compileJava.dependsOn it
doLast {
def snapshots = compileJava.classpath
.filter { project.ext.isRelease || !(it.path ==~ /(?i)${project.rootProject.projectDir.toString().replace('\\', '\\\\')}.*build.libs.*/) }
.filter { it.path =~ /(?i)-SNAPSHOT/ }
.collect { it.name }
.unique()
if (!snapshots.isEmpty()) {
throw new GradleException("Please get rid of snapshots for following dependencies before releasing $snapshots")
}
}
})
}
}
I need some help in generifying this snippet to be applicable to all types of dependencies(not just java)
Thanks!
L.E. Could something like this work?
https://discuss.gradle.org/t/how-can-i-check-for-snapshot-dependencies-and-throw-an-exception-if-some-where-found/4064
So I got it working by tweaking a bit the response of #lance-java, it looks something like:
Task snapshotCheckingTask = project.tasks.create('snapshotCheckingTask', {
doLast {
def snapshots = new ArrayList()
def projectConfigurations = project.configurations.findAll { true }
projectConfigurations.each {
if (it.isCanBeResolved()) {
it.resolvedConfiguration.resolvedArtifacts.each {
if (it.moduleVersion.id.version.endsWith('-SNAPSHOT')) {
snapshots.add(it)
}
}
}
}
if (!snapshots.isEmpty()) {
throw new GradleException("Please get rid of snapshots for following dependencies before releasing $snapshots")
} else {
throw new GradleException("Hah, no snapshots!")
}
}
})
project.tasks.release.dependsOn snapshotCheckingTask
cc #Eugene
P.S. However, this does not take into account .net dependencies
Something like
Collection<ResolvedArtifact> snapshotArtifacts = project.configurations*.resolvedConfiguration.resolvedArtifacts.filter { it.moduleVersion.id.version.endsWith('-SNAPSHOT') }
if (!snapshotArtifacts.empty) {
// throw exception
}
See
https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/Configuration.html#getResolvedConfiguration--
https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedConfiguration.html#getResolvedArtifacts--
Here's what I came up with using Gradle 7.2 and the modern Kotlin DSL:
tasks.register("releaseEnforcement") {
group = "verification"
description = "Check whether there are any SNAPSHOT dependencies."
doLast {
val violations = project.configurations
.filter { it.name == "compileClasspath" || it.name == "runtimeClasspath" }
.flatMap { configuration ->
configuration.resolvedConfiguration.resolvedArtifacts
.map { it.moduleVersion.id }
.filter { it.version.endsWith("-SNAPSHOT") }
}
.toSet()
if (violations.isNotEmpty()) {
error("Snapshot dependencies found:\n\n${violations.joinToString(separator = "\n")}")
}
}
}

How to conditionally apply source set excludes to build based on parameter provided

I'm using Gradle 4.4 (edit: issue still present in Gradle 4.8). For reasons my project has the following layout
src/main/java/com/company/common
src/main/java/com/company/mod1
src/main/java/com/company/mod2
The build can generate either mod1 or mod2, depending on the build task executed. Classes from mod1 shall never use classes from mod2 and vice versa. Therefore I want the build to fail, if either uses classes from the other. However I still want to be able to develop both sources in Eclipse, which is why I only want the build to fail on our CI server. The CI server provides a parameter CI_BUILD. The build file uses the following mechanism to allow this:
Excludes aren't applied properly here:
ext {
ext_template_mod1 = [:]
ext_template_mod1.src_excludes = "**/mod2/**"
ext_template_mod2 = [:]
ext_template_mod2.src_excludes = "**/mod1/**"
if (project.hasProperty("mod2")) {
ext_template = ext_template_mod2
} else {
ext_template = ext_template_mod1
}
}
sourceSets {
main {
java {
if (project.hasProperty("CI_BUILD")) {
exclude "${project.ext_template.src_excludes}"
}
}
}
}
For some reason this doesn't work. gradlew build -PCI_BUILD doesn't fail if a source file on mod1 references a source file from mod2.
I fail to understand why it doesn't. If I don't check for the project property, the exclude works as expected:
Working configuration:
ext {
ext_template_mod1 = [:]
ext_template_mod1.src_excludes = "**/mod2/**"
ext_template_mod2 = [:]
ext_template_mod2.src_excludes = "**/mod1/**"
if (project.hasProperty("mod2")) {
ext_template = ext_template_mod2
} else {
ext_template = ext_template_mod1
}
}
sourceSets {
main {
java {
exclude "${project.ext_template.src_excludes}"
}
}
}
Now gradlew build -PCI_BUILD fails as expected when a source file on mod1 references a source file from mod2.
But now my IDE won't recognize the sources in the mod2 folder as sources anymore.
How can I apply excludes to my source set based on the existence of a build parameter?
I've been creating a minimal example and it works just fine there:
apply plugin: 'java'
ext {
ext_template_mod1 = [:]
ext_template_mod1.src_excludes = "**/mod2/**"
ext_template_mod2 = [:]
ext_template_mod2.src_excludes = "**/mod1/**"
if (project.hasProperty("mod2")) {
ext_template = ext_template_mod2
} else {
ext_template = ext_template_mod1
}
}
sourceSets {
main {
java {
if (project.hasProperty("CI_BUILD")) {
exclude "${project.ext_template.src_excludes}"
}
}
}
}
jar {
if (project.hasProperty("CI_BUILD")) {
exclude "${project.ext_template.src_excludes}"
}
}
The issue above was an artifact of my own build. I had used dynamic task generation and forgotten to provide the parameter I was checking for as a startParameter.projectProperties.

Android Test Instrumentation Runner Arguments set during execution phase

I am trying to create gradle tasks to run only a sub set of tests based on annotations, which is detailed in the gradle dsl. I am trying to set the testInstrumentationRunnerArguments during the gradle execution phase. While I am successfully setting the arguments it doesn't appear to actually do any filtering.
We do have the tests located in a different source set path but we have added them to the default androidTest source set. The tests all run they are simply not filtered.
My questions then become: Can the test arguments be dynamically set during the execution phase of gradle or do they need to be set in the configuration phase? If they can be set during execution can any one point out why the below gradle wont work?
Here is my test.gradle which is applied after the android library configuration closure.
/*
* Copyright (c) 2018 company. All rights reserved.
*/
ext {
integrationTest = false
networkIntegrationTest = false
}
android.sourceSets {
androidTest {
java {
// add our custom test source sets so android studio gives us good tooling
srcDirs += './src/integrationNetworkTest/java'
srcDirs += './src/integrationTest/java'
}
res {
srcDirs += './src/integrationNetworkTest/res'
srcDirs += './src/integrationTest/res'
}
}
}
tasks.whenTaskAdded { task ->
def name = task.name.toLowerCase()
if(name.contains('connectedcheck') || task.name.matches("connected([a-zA-Z]?)+AndroidTest")) {
task.doFirst {
if(!integrationTest && !networkIntegrationTest) {
android.productFlavors.each {
it.testInstrumentationRunnerArguments.put('notAnnotation',
'com.company.android.infrastructure.test.IntegrationTest,com.company.android.infrastructure.test.IntegrationNetworkTest')
}
}
else {
StringBuilder sb = new StringBuilder()
if(integrationTest && networkIntegrationTest) {
sb.append('com.company.android.infrastructure.test.IntegrationTest')
sb.append(',')
sb.append('com.company.android.infrastructure.test.IntegrationNetworkTest')
}
else if (integrationTest) {
sb.append('com.company.android.infrastructure.test.IntegrationTest')
}
else if (networkIntegrationTest) {
sb.append('com.company.android.infrastructure.test.IntegrationNetworkTest')
}
if(integrationTest || networkIntegrationTest) {
android.productFlavors.each {
it.testInstrumentationRunnerArguments.put('annotation', sb.toString())
}
}
}
android.productFlavors.each {
println "${it.name} Test Annotations: ${it.testInstrumentationRunnerArguments}"
}
}
}
}
project.afterEvaluate {
android.libraryVariants.each { variant ->
tasks.create("${variant.name}IntegrationTest") {
group 'company'
description 'Run company Integration tests with only network requests mocked'
finalizedBy "connected${variant.name.capitalize()}AndroidTest"
doLast {
integrationTest = true
}
}
tasks.create("${variant.name}IntegrationNetworkTest") {
group 'company'
description 'Run company Integration tests with real network requests'
finalizedBy "connected${variant.name.capitalize()}AndroidTest"
doLast {
networkIntegrationTest = true
}
}
}
}
Gradle: 4.1
Android Plugin: 3.0.1
*Edit: * I think its a bug: Android Bug Tracker

Categories