https://gradle.com/ logo
Join Slack
Powered by
# plugin-development
  • k

    Kelvin Chung

    07/03/2025, 6:35 AM
    Hey folks. I have a question loosely related to how things work with the Kotlin Multiplatform Plugin: I currently have this code:
    Copy code
    val testResultsElements = configurations.consumable("testResultsElementsForJvmTest") {
        // This configuration mimics that defined by the "test-suite-base" plugin
        description = "Binary results obtained from running the 'jvmTest' suites."
    
        attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.VERIFICATION))
        attributes.attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, objects.named(VerificationType.TEST_RESULTS))
        // FIXME how do we accommodate multiple JVM targets and their respective test suites?
        attributes.attribute(TestSuiteName.TEST_SUITE_NAME_ATTRIBUTE, objects.named("test"))
    
        val binaryDir = tasks.named<Test>("jvmTest").flatMap { it.binaryResultsDirectory }
        outgoing.artifact(binaryDir) {
            type = ArtifactTypeDefinition.DIRECTORY_TYPE
        }
    }
    I'm wondering what I would have to change if I had two source sets that produced JVM code, so I would have two "testResults" configurations. And how that would relate to the test result aggregation plugin since I would need to differentiate them in such a way that the aggregation plugin could pick each of them separately.
    v
    • 2
    • 18
  • v

    Vampire

    07/09/2025, 11:28 PM
    Is there some recipe somewhere I forgot how to make a task / extension where the consumer can configure a copy-spec that is then accounted as input files for the task and can be used with
    with
    in the task implemenation?
    y
    • 2
    • 5
  • y

    ysb33r

    07/13/2025, 7:17 PM
    There are a number of external tools that produce results in JUnit XML and which can be called from Gradle. One example being
    terraform test -junit-xml
    . What I would like to do is to convert those XML files into HTML reports that look-and-feel the same as the ones generated by
    Test
    tasks. Does anyone know whether that can be achieved with what we have available in the Gradle API today (7.3+)?
    m
    s
    v
    • 4
    • 12
  • t

    Thomas Broyer

    07/15/2025, 3:27 PM
    How do you handle exceptions in WorkActions? I have a WorkAction that runs in a classLoaderIsolation or processIsolation WorkQueue, the code inside the WorkAction can throw exceptions. One of these exceptions (from a third-party library) computes its
    getMessage()
    lazily using a helper class (from that same third-party lib). The problem is that, if I don't do anything special, Gradle will somehow "copy" the exception outside of the classLoaderIsolation or back to the Gradle Daemon process, but won't "copy" that helper class, so when Gradle wants to print the error message to the console and calls the exception's
    getMessage()
    method, that one will throw a ClassNotFoundException that will shadow the actual error. How would you recommend handling this?
    ➕ 1
    t
    m
    • 3
    • 9
  • t

    Thomas Broyer

    07/16/2025, 3:09 PM
    Should
    ConfigurableFileCollection
    properties be initialized with
    .convention()
    or
    .from()
    ? What do you think? (I initially went with
    .convention()
    , but when the conventional value is a bit convoluted to compute –a file in the output of the
    processResources
    task– it's asking too much to the users if they just want to add to the file collection, while on the other hand if initialized with
    .from()
    and they don't want that value they can
    .setFrom()
    to reinitialize it; what would be the use cases for
    .convention()
    then?)
  • t

    Thomas Broyer

    07/16/2025, 3:40 PM
    Similar question about `MapProperty`: would you rather initialize it with
    convention()
    or
    putAll()
    ? (my use-case is initializing it with
    providers.gradlePropertiesPrefixedBy(…)
    ) I've found one instance of MapProperty in the Gradle codebase that's not left empty, and it's in the build logic (so not in a "public" plugin), and it uses a few calls to `put()`: https://github.com/gradle/gradle/blob/56ac845808133d19b7e03a3fcc1264f21122e37e/bui[…]/src/main/groovy/gradlebuild/docs/GradleReleaseNotesPlugin.java
    m
    s
    • 3
    • 8
  • m

    Martin

    07/17/2025, 12:03 PM
    MapProperty
    questions: • Is it possible to have
    null
    as a value? • Having
    MapProperty<String, Any?>
    displays an error in my IDE but compiles fine. Where is the disconnect? How come the KIJP knows that the bound is non nullable?
    v
    • 2
    • 25
  • j

    Jerome Haltom

    07/17/2025, 2:04 PM
    Hello. I have a plugin which adds an extension, upon which various settings can be configured by the user. Based on those settings I need to generate 1 or more tasks. The tasks only exist if the settings say they should. What I am having trouble doing is figuring out exactly where this code for building the tasks should be. I tried, on my custom extension objects, to do it in configureEach: except this seems to run before the user's build.gradle.kts file is processed, so the options the user sets aren't yet available. I did get it working by doing my work in project afterEvalute. But this is not ideal. Maybe I'm misunderstanding how this is supposed to be done.
    m
    t
    +4
    • 7
    • 100
  • a

    Alexey Loubyansky

    07/22/2025, 4:24 PM
    Adding a component variant w/o introducing ambiguity I am trying to add a component variant which uses
    runtime
    or
    runtimeElements
    as a base variant but that introduces an ambiguity, which i'm struggling to find a way to avoid. Is there an example how it's supposed to be done? Here is my playground plugin https://github.com/aloubyansky/playground/blob/gradle-comp-variants/plugin/src/main/java/org/example/PlaygroundPlugin.java And here is the outcome of running it https://github.com/aloubyansky/playground/tree/gradle-comp-variants. Thanks!
    t
    v
    • 3
    • 56
  • a

    Alexey Loubyansky

    07/23/2025, 8:49 PM
    Configuration.copyRecursive() and test-fixtures I'm trying to create a recursive copy of
    testRuntimeClasspath
    , resolve it and iterate through the resolved artifacts. But if there are test fixtures, it fails with
    Copy code
    > Could not resolve all artifacts for configuration ':app:testRuntimeClasspathCopy'.
       > Could not resolve project :app.
         Required by:
             project :app
          > Unable to find a variant with the requested capability: feature 'test-fixtures':
               - Variant 'testRuntimeClasspathCopy' provides 'playground:app:unspecified'
    It looks like something is missing from the copy of the original configuration. Is this expected? Thanks! I have a little playground project to reproduce the issue here https://github.com/aloubyansky/playground/tree/gradle-copyRecursive-test-fixtures Commenting out https://github.com/aloubyansky/playground/blob/gradle-copyRecursive-test-fixtures/plugin/src/main/java/org/example/GreetingTask.java#L21 works. (I realize referencing a
    Project
    from a task is a bad practice, this is just a reproducer).
    o
    v
    • 3
    • 12
  • j

    Jakub Chrzanowski

    07/28/2025, 7:38 PM
    Hey, folks! In my IntelliJ Platform Gradle Plugin, I've decided to bump the Gradle version from
    8.14.3
    to
    9.0.0-rc-4
    . When running integration tests against Gradle 8.x, the plugin fails on
    java.lang.NoSuchMethodError
    and
    java.lang.NoClassDefFoundError
    . In my code, I rely on
    sequenceOf()
    and
    yield()
    , whose signatures slightly changed in Kotlin 2.0:
    sequenceOf()
    Copy code
    * Exception is:
    java.lang.NoSuchMethodError: 'kotlin.sequences.Sequence kotlin.sequences.SequencesKt.sequenceOf(java.lang.Object)'
    	at org.jetbrains.intellij.platform.gradle.resolvers.path.ModuleDescriptorsPathResolver.<init>(ModuleDescriptorsPathResolver.kt:19)
    	at Build_gradle.<init>(build.gradle.kts:53)
    yield()
    Copy code
    * Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':signPlugin'.
    ....
    Caused by: java.lang.NoClassDefFoundError: kotlin/coroutines/jvm/internal/SpillingKt
    	at org.jetbrains.intellij.platform.gradle.tasks.SignPluginTask$arguments$1.invokeSuspend(SignPluginTask.kt:208)
    	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    My first though was to build the Gradle plugin targeting Kotlin 1.8 (we support Gradle 8.5+) using:
    Copy code
    kotlin {
        jvmToolchain(17)
    
        compilerOptions {
            apiVersion = KotlinVersion.KOTLIN_1_8
            languageVersion = KotlinVersion.KOTLIN_1_8
        }
    }
    but this didn't really help. Any ideas on how to deal with that?
    m
    v
    v
    • 4
    • 21
  • k

    Kelvin Chung

    07/28/2025, 9:03 PM
    Question: Has there been any long-term thought into reworking
    RepositoryHandler
    so that it has more conventional
    NamedDomainObjectContainer
    semantics? It just seems like with the way Gradle has evolved over the years, something like
    Copy code
    repositories {
      maven(url) { name = "foobar" }
    }
    would be a bit clunky compared to
    Copy code
    repositories.register<MavenArtifactRepository>("foobar") {
      url.set(...)
    }
    Of course, I recognize that any effort to go in that direction would be nontrivial, since the API appears to not have a thing that is both "ordered" and "an extensible container", but one can dream, right?
  • e

    efemoney

    07/31/2025, 9:53 AM
    Hello everyone, I would like to know, foe each dependency in a project for instance, what repository it comes from. Is it possible to get this information from gradle (even internal APIs are fine with me)?
    n
    v
    t
    • 4
    • 15
  • s

    Steve Ebersole

    07/31/2025, 6:01 PM
    Having an interesting situation I cannot understand. As part of a plugin I register the following DSL extensions:
    Copy code
    public abstract class HibernateOrmSpec {
    	...
    
    	@Optional
    	abstract public Property<EnhancementSpec> getEnhancement();
    
    	public void enhancement(Action<EnhancementSpec> action) {
    		EnhancementSpec spec = getObjectFactory().newInstance( EnhancementSpec.class );
    		action.execute( spec );
    		getEnhancement().set( spec );
    	}
    }
    
    abstract public class EnhancementSpec {
    	abstract public Property<Boolean> getEnableLazyInitialization();
    	...
    }
    Later, I try to see if the user specified this "enhancement" extension :
    if ( !getEnhancement().isPresent() ) ...
    The odd part is this... This works as expected:
    Copy code
    hibernate {
        enhancement {
        }
    }
    However, this does not:
    Copy code
    hibernate {
        enhancement
    }
    Now I can easily accept that this second form is "not valid". But the problem I am having is actually being able to recognize this situation to properly handle it (whether that be treat it as the first form, throw an exception, etc.). The problem is that, as far as I can tell, Gradle just ignores it (it being the "enhancement" token). Its not added to ext properties or anything like that. Any idea what Gradle actually does with that?
    o
    • 2
    • 3
  • j

    Jerome Haltom

    07/31/2025, 7:40 PM
    Are there any services to consume to help implementing a per-file compilation avoidance cache? For a custom language or process.
    p
    v
    • 3
    • 18
  • y

    ysb33r

    08/08/2025, 3:26 PM
    Does
    -i
    affect a task's class path?
    That is the headline question, let me explain. I was testing up-to-date status of some tasks in plugin using testKit. Effectively, what I was doing in the test is something like
    Copy code
    GradleRunner.create().withArguments('task1')
    GradleRunner.create().withArguments('task2')
    basically
    task2
    depend on
    task1
    which depends on
    task0
    Thus in the first invocation
    task0
    and
    task1
    will have an outcome of
    SUCCESS
    . In the 2nd invocation, those two should have an outcome of
    UP_TO_DATE
    , and
    task2
    will be
    SUCCESS
    . Not problem with that, until I did...
    Copy code
    GradleRunner.create().withArguments('task1')
    GradleRunner.create().withArguments('task2', '-i')
    Suddenly, on the 2nd invocation the outcome of
    task0
    was
    SUCCESS
    instead of
    UP_TO_DATE
    and Gradle logs stated that
    Copy code
    Task ':task0' is not up-to-date because:
      Class path of task ':task0' has changed from 58046a25460590d169a1847f193c5177 to b72b7af69ca1f7306a1e3a2dcf54a21a.
    I find that unexpected behaviour. WDYT?
    👀 2
    v
    • 2
    • 3
  • v

    Vladislav Chesnokov

    09/03/2025, 10:14 AM
    Hey folks! The Plugin Publishing Plugin has been updated to version 2.0.0. The plugin is now compatible with Configuration Cache. Note that the signing task is compatible with Configuration Cache only starting from Gradle 8.1, so it's required to use at least Gradle 8.1.1 for signed publications if you want full Configuration Cache compatibility. Provider API is now used for all configuration properties. There is no impact on users, and straightforward scenarios will continue to work as before, but you'll need to update your build scripts if you did something advanced. The support for old Gradle versions has been removed. The minimum supported Gradle version is now 7.4. Also, bundled dependencies have been updated.
    party gradlephant 6
    n
    • 2
    • 2
  • y

    ysb33r

    09/09/2025, 8:36 PM
    Scatter gun question: Has anyone done a build service which uses TestContainers to fire up a container and then have the build service interact with it?
    r
    t
    • 3
    • 5
  • r

    René

    09/09/2025, 8:42 PM
    im curious about the usecase
    🧵 1
  • j

    John

    09/16/2025, 8:52 PM
    what is the best way to access a service from a WorkAction?
    p
    m
    • 3
    • 6
  • b

    Boris Petrov

    09/19/2025, 7:32 AM
    Hi all, I'm trying to get Gretty work on Gradle 9. I believe I've fixed the deprecation warnings but I get a very strange error when running with Gradle 9 (the reason is most likely Groovy 4):
    Copy code
    > No signature of method: org.akhikhl.gretty.LauncherBase.beforeLaunch() is applicable for argument types: () values: []
      Possible solutions: beforeLaunch(), afterLaunch()
    You can see in the code that there very much is such a method on that class. Even the
    Possible solutions
    below the error mention it. The error happens on the
    super.beforeLaunch()
    call in DefaultLauncher. The details are not important that much - it's just that every call to
    super.XXX()
    (not just here - also in other places) in Gradle 9/Groovy 4 fails with
    No signature of method ... is applicable for argument types
    . Something which doesn't happen in Gradle 8/Groovy 3. Does anyone have any idea why that might be? P.S. Something of note - the plugin is compiled with Gradle 6 (so I think Groovy 2) (for backwards-compatibility). Not sure if it matters. I see the compiled code in the JAR:
    Copy code
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    Object object = arrayOfCallSite[6].callStatic(DefaultLauncher.class, this.project);
    this.runnerClasspath = (Collection<URL>)ScriptBytecodeAdapter.castToType(object, Collection.class);
    ScriptBytecodeAdapter.invokeMethodOnSuper0(LauncherBase.class, (GroovyObject)this, "beforeLaunch");
    Not sure if this somehow "broke" in Gradle 9/Groovy 4?
    v
    o
    • 3
    • 40
  • j

    Jonathan Leitschuh

    09/23/2025, 10:13 PM
    Interesting to not see the Gradle Plugin Portal on this list https://openssf.org/blog/2025/09/23/open-infrastructure-is-not-free-a-joint-statement-on-sustainable-stewardship/
    m
    t
    • 3
    • 2
  • j

    Jonathan Leitschuh

    09/25/2025, 8:12 PM
    Question to plugin authors. What's the best way to integration test the problems API?
    v
    j
    • 3
    • 6
  • k

    Kelvin Chung

    09/30/2025, 12:27 AM
    Question: Is there an accepted way to obtain an
    IncludedBuild
    object from
    gradle.buildPath
    ? It's mainly so that I can get the build's project directory, relative to the root build's project directory, for an internal plugin.
    v
    • 2
    • 6
  • n

    Niels Doucet

    09/30/2025, 1:50 PM
    Either the api for
    MapProperty
    is confusing, or I'm doing something wrong. There seems to be a difference in behavior between using
    putAll
    vs individual
    put
    invocations when setting lazy
    Provider
    instances as values for these keys. Details in thread 🧵
    m
    m
    a
    • 4
    • 8
  • j

    Jeff Wise

    10/02/2025, 3:09 PM
    I'm using the Open Policy Agent in a project. It has its own non-JVM testing framework. See https://www.openpolicyagent.org/docs/policy-testing. I can create custom tasks to run the tests, but the tests do not show up in the Gradle build scan. I believe I must create a custom Test task for that but the Test task expects JVM classes or else it says NO-SOURCE and doesn't run the tests. What is the recommended way to run non-JVM tests and have them show up in the Gradle build scan?
    o
    t
    v
    • 4
    • 8
  • j

    Jonathing

    10/03/2025, 12:37 PM
    Hey folks. Is it possible to define dependency constraints (or just dependency) attributes for a dependency so as to not pollute the consumable configurations (i.e.
    runtimeElements
    )? To make a long story short, I'm using artifact transforms to apply workspace-only changes to dependencies, but I don't want those attributes to appear in the resulting module metadata in publications.
    v
    • 2
    • 4
  • j

    Jonathing

    10/04/2025, 7:18 PM
    👋 Hey folks! Quick question, I'm banking on someone hopefully knowing the answer. Can dependency substitution rules stack? For example, if I have this (pseudo-code):
    Copy code
    configurations.compileClasspath.dependencySubstitutions {
        substitute module('org.example:examplelib') using variant(module('org.example:examplelib')) {
            attributes { attribute(MyAttribute.ATTR_1, true) }
        }
    
        substitute module('org.example:examplelib') using variant(module('org.example:examplelib')) {
            attributes { attribute(MyAttribute.ATTR_2, true) }
        }
    }
    Would the resolver try to select the dependency in a way where both ATTR_1 and ATTR_2 are true? (I can obviously test it myself but am just checking to see if anyone knew the answer to this before I did some experiments)
    • 1
    • 1
  • j

    Jonathing

    10/15/2025, 12:52 PM
    Hey folks. I've noticed a few interesting things about how other plugins use injected services. Notably, how some plugins in the wild inject types such as
    Project
    ,
    ConfigurationContainer
    , and
    SoftwareComponentFactory
    , despite not being listed in the Service Injection userguide page. So my question is, is this page out-of-date or is injecting those other types unsafe? Does Gradle hold the right to make injecting type
    Project
    stop working at any time?
    m
    v
    • 3
    • 18
  • r

    Rahul Srivastava

    10/27/2025, 8:05 AM
    hi folks i am using
    Copy code
    /**
     * Copyright © 2016-2025 The Thingsboard Authors
     *
     * 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.apache.tools.ant.filters.ReplaceTokens
    
    plugins {
        id "nebula.ospackage" version "8.6.3"
    }
    
    buildDir = projectBuildDir
    version = projectVersion
    distsDirName = "./"
    
    // OS Package plugin configuration
    ospackage {
        packageName = pkgName
        version = "${project.version}"
        release = 1
        os = LINUX
        type = BINARY
    
        into pkgInstallFolder
    
        user pkgUser
        permissionGroup pkgUser
    
        // Copy the actual .jar file
        from(mainJar) {
            // Strip the version from the jar filename
            rename { String fileName ->
                "${pkgName}.jar"
            }
            fileMode 0500
            into "bin"
        }
    
        if("${pkgCopyInstallScripts}".equalsIgnoreCase("true")) {
            // Copy the install files
            from("${buildDir}/bin/install/install.sh") {
                fileMode 0775
                into "bin/install"
            }
    
            from("${buildDir}/bin/install/upgrade.sh") {
                fileMode 0775
                into "bin/install"
            }
    
            from("${buildDir}/bin/install/logback.xml") {
                into "bin/install"
            }
        }
    
        // Copy the config files
        from("${buildDir}/conf") {
            exclude "${pkgName}.conf"
            fileType CONFIG | NOREPLACE
            fileMode 0754
            into "conf"
        }
    
        // Copy the data files
        from("${buildDir}/data") {
            fileType CONFIG | NOREPLACE
            fileMode 0754
            into "data"
        }
    
        // Copy the extensions files
        from("${buildDir}/extensions") {
            into "extensions"
        }
    }
    
    // Configure our RPM build task
    buildRpm {
    
        arch = NOARCH
    
        archiveVersion = projectVersion.replace('-', '')
        archiveFileName = "${pkgName}.rpm"
    
        requires("(java-17 or java-17-headless or jre-17 or jre-17-headless)") // .or() notation does work in RPM plugin
    
        from("${buildDir}/conf") {
            include "${pkgName}.conf"
            filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm'])
            fileType CONFIG | NOREPLACE
            fileMode 0754
            into "${pkgInstallFolder}/conf"
        }
    
        preInstall file("${buildDir}/control/rpm/preinst")
        postInstall file("${buildDir}/control/rpm/postinst")
        preUninstall file("${buildDir}/control/rpm/prerm")
        postUninstall file("${buildDir}/control/rpm/postrm")
    
        user pkgUser
        permissionGroup pkgUser
    
        // Copy the system unit files
        from("${buildDir}/control/template.service") {
            addParentDirs = false
            fileMode 0644
            into "/usr/lib/systemd/system"
            rename { String filename ->
                "${pkgName}.service"
            }
        }
    
        link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
        link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
    }
    
    // Same as the buildRpm task
    buildDeb {
    
        arch = "all"
    
        archiveFileName = "${pkgName}.deb"
    
        requires("openjdk-17-jre").or("java17-runtime").or("oracle-java17-installer").or("openjdk-17-jre-headless")
    
        from("${buildDir}/conf") {
            include "${pkgName}.conf"
            filter(ReplaceTokens, tokens: ['pkg.platform': 'deb'])
            fileType CONFIG | NOREPLACE
            fileMode 0754
            into "${pkgInstallFolder}/conf"
        }
    
        configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf")
        configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml")
        configurationFile("${pkgInstallFolder}/conf/logback.xml")
        configurationFile("${pkgInstallFolder}/conf/actor-system.conf")
    
        preInstall file("${buildDir}/control/deb/preinst")
        postInstall file("${buildDir}/control/deb/postinst")
        preUninstall file("${buildDir}/control/deb/prerm")
        postUninstall file("${buildDir}/control/deb/postrm")
    
        user pkgUser
        permissionGroup pkgUser
    
        // Copy the system unit files
        from("${buildDir}/control/template.service") {
            addParentDirs = false
            fileMode 0644
            into "/lib/systemd/system"
            rename { String filename ->
                "${pkgName}.service"
            }
        }
    
        link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml")
        link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf")
    }
    build.gradle and encountering error
    startup failed:
    General error during conversion: Unsupported class file major version 69 while compiling i am using openjdk version 17 i need help in this i am running this on mac machine
    t
    p
    v
    • 4
    • 3