Gradle无法从应用的子脚本中识别插件任务

Gradle无法从应用的子脚本中识别插件任务,gradle,build.gradle,gradle-kotlin-dsl,Gradle,Build.gradle,Gradle Kotlin Dsl,我真的很想将我的脚本模块化为多个部分(测试、默认、文档、android)部分。但当我尝试将子脚本应用于父脚本时,我遇到了问题 这是我的子脚本(此时IDE没有抱怨) 但当我尝试将此脚本应用于主脚本时: apply(from = "tdd.gradle.kts") 我得到以下错误: Script compilation errors: Line 01: import io.gitlab.arturbosch.detekt.Detekt ^ Unre

我真的很想将我的脚本模块化为多个部分(测试、默认、文档、android)部分。但当我尝试将子脚本应用于父脚本时,我遇到了问题

这是我的子脚本(此时IDE没有抱怨)

但当我尝试将此脚本应用于主脚本时:

apply(from = "tdd.gradle.kts")
我得到以下错误:

    Script compilation errors:

  Line 01: import io.gitlab.arturbosch.detekt.Detekt
                  ^ Unresolved reference: io

  Line 10:  val detekt by existing(Detekt::class) {
                                   ^ Unresolved reference: Detekt

  Line 11:      reports {
             ^ Unresolved reference: reports

  Line 12:          html {
              ^ Unresolved reference: html
如何解决此问题,以便在子脚本中应用detekt插件并将其应用于parrent脚本?

(情况与您的情况非常相似):

脚本插件无法访问主buildscript类路径

您可以使用以下命令将对mycompany.plugin的依赖项添加到tests.gradle buildscript{}语法


因此,基本上,使用旧的
buildscript
语法,您的导入应该可以工作

我已经尝试了很多方法来破解这个问题。它的长短在Kotlin DSL中是不可能的

未经测试:我会尝试的方法(bc这是我还没有尝试过的一件事)是使用插件的两个应用程序(老式)并将应用脚本插件中的所有项目配置为显式的跨项目配置,这种配置不需要在应用脚本中使用
所有项目
子项目

您应该参考gradle当前的项目文档,但通常采用以下形式:

project(":foo") {
    // Some configuration here
    apply(plugin = "plugin.id.here.sample-plugin")
    // Then use configuration techniques in the Kotlin DSL guide for configuring when you don't have access to type-safe accessors, small example below
    configure<SamplePluginExtension> {
        // Plugin extension configuration...
    }
}
build.gradle.kts

这类lambda的一些一般安全提示:

  • 项目
    实例使用
    p
    将确保使用它的开发人员不需要考虑当前范围内的项目实例(在应用的脚本中或调用它的位置)。这也意味着调用站点的封闭作用域提供的
    project
    仍然可以使用
  • 大量考虑回归单元提供无副作用的用法。一些插件具有扩展,可以通过注册在容器的config块中找到的所有实例来自动配置容器的各种元素。除非绝对需要,否则不支持隐式链接是最安全的选择
  • 请记住,在许多上下文中,不能依赖标准库中的kotlin的
    T.apply{}
    ,因为它将解析为Gradle的
    apply
    ,因此如果您想这样做(或返回到单元)轻松跨越多个配置lambda,最好创建一个小的helper函数,基本上实现apply在Kotlin中的功能,或者像我所做的那样,构建一个helper,运行给定的块并始终返回单元(或某种泛化类型)

@ToYonos-gradle的建议对Kotlin DSL不起作用。我的结果很差(我专门使用了
类路径
依赖项),因为gradle会报告插件从未应用过,并且在同一条错误消息中显示扩展已应用。我粗略的猜测是,这与字节码和/或为应用脚本使用单独的类装入器有关。(很抱歉,我本想在你的回答中添加这一点,但没有足够的代表性)

我的回答建立在fuzzyweapon的回答之上,因为我在看到它之前无法理解它,太好了

我的答案基于gradledsl语法。我希望有足够的细节来收集适用于Kotlin DSL的解决方案

您可以通过创建接受项目对象参数的闭包来解决大多数此类问题,而不是将构建逻辑直接放入应用的脚本中(尽管这显然适用于独立于项目的逻辑)。出于我不清楚的原因,在单独的脚本中创建新函数会导致项目对象参数参数参数始终为null。闭包的语法非常接近,几乎与Java lambda语法相同,从调用方的角度来看,函数和闭包之间的语义基本相同(显然与外部脚本内部不同)

我最近不得不解决一个类似的问题,我想将一些动态任务生成和清理逻辑提取到一个单独的脚本中。我就是这样解决的:

  • 将逻辑移到一个单独的文件中,在我的例子中,我在
    $projectDir/gradle/env config.gradle
  • 将其包装在参数化闭包中
  • 修正你(OP)在问题中提出的错误。这些问题中的大多数都可以使用直接导入来解决,就像在Java、Kotlin或Groovy中一样。这可能需要,特别是对于二进制插件,buildscript依赖项,正如您将在下面的完整示例中看到的那样
  • 将闭包引用附加到扩展脚本插件对象
  • 将项目对象作为参数从提取逻辑的构建脚本中调用闭包
  • 下面是一个基于我解决该问题所做工作的简短示例,其中我定义了任务创建和任务逻辑,然后将对闭包的引用外部化,以便可以从应用该脚本的脚本中调用闭包:

    $projectDir/gradle/env config.gradle
    中:

    然后在项目构建脚本的适当范围和阶段调用它,在我的例子中,这是在配置阶段的自定义转换任务中:

    $projectDir/build.gradle
    中:

    我发现,这种分解构建脚本的方法非常灵活,基本上可以处理希望将逻辑分解为单独脚本的大多数情况

    下面是最终产品独立构建脚本的精简版本(实际上,它甚至在自定义脚本中应用了不同的自定义脚本):


    我知道这有点晚了,但也许可以帮助那些面对大型构建文件或需要模块化构建的人。

    你能分享你的
    buildscript
    block吗?@ToYonos:我没有,因为它是
    project(":foo") {
        // Some configuration here
        apply(plugin = "plugin.id.here.sample-plugin")
        // Then use configuration techniques in the Kotlin DSL guide for configuring when you don't have access to type-safe accessors, small example below
        configure<SamplePluginExtension> {
            // Plugin extension configuration...
        }
    }
    
    val allProjectsConfiguration by extra { p: Project ->
        // Some project configuration here...
    }
    
    //Boilerplate build script dependencies, repositories, and buildscript block as necessary...
    val allProjectsConfiguration: (Project) -> Unit by extra
    allProjects {
        allProjectsConfiguration(this)
    // where `this` is not the host script's project, not the applied script's project (of course), but one of the given projects in *all* of the projects.
    
    def genTasks = { p ->
        def previousServicePropsTask = null
        
        deploymentPlatforms.each {
            def deploymentPlatform = it
            
            deploymentEnvironments.each {
                def deploymentEnv = it
                def propsTaskName = "xslt_${deploymentPlatform}_service_props_$deploymentEnv"
                
                p.logger.info("Creating XSL Transform task: $propsTaskName")
                
                def newServicePropsXslTask = p.tasks.create(
                    [name: propsTaskName, type: SaxonXsltTask], {
                        input "${p.projectDir}/dist/config/$deploymentPlatform/config.properties.xml"
                        stylesheet "${p.projectDir}/transforms/build/apply-env-specific-service-properties.xsl"
                        output "${p.projectDir}/config/$deploymentPlatform/$deploymentEnv/config.properties"
                        parameters(envSpecificServicePropsXslTransformParameters[deploymentPlatform][deploymentEnv])
                    }
                )
                
                if (previousServicePropsTask != null) {
                    newServicePropsXslTask.dependsOn(previousServicePropsTask)
                }
                
                previousServicePropsTask = newServicePropsXslTask
            }
        }
        
        return previousServicePropsTask
    }
    
    //... other stuff
    
    ext {
        //Add closure to script plugin extension object
        generateTransformTasks = genTasks
    }
    
    apply from: "$projectDir/gradle/env-config.gradle"
    
    task transformConfigurations() {
        def taskDepends = generateTransformTasks(project)
        dependsOn(taskDepends)
    }
    
    buildscript {
        repositories {
            mavenLocal()
        }
        dependencies {
            classpath 'gradle.plugin.com.github.eerohele:saxon-gradle:0.8.0'
        }
    }
    
    import com.github.eerohele.SaxonXsltTask
    import java.nio.charset.StandardCharsets
    import java.nio.file.Files
    import java.nio.file.Paths
    import java.util.Properties
    
    def deploymentPlatforms = [
        'platX'
    ]
    
    def deploymentEnvironments = [
        'dev',
        'qa',
        'prod'
    ]
    
    def envSpecificLog4j2XslTransformParameters = [:]
    def envSpecificServicePropsXslTransformParameters = [:]
    
    //Have to use fully-qualified class name here, known issue in Gradle
    apply plugin: com.github.eerohele.SaxonPlugin
    
    deploymentPlatforms.each {
        def plat = it
        
        envSpecificLog4j2XslTransformParameters[plat] = [:]
        envSpecificServicePropsXslTransformParameters[plat] = [:]
        
        deploymentEnvironments.each {
            Properties log4j2Props = new Properties()
            Properties serviceProps = new Properties()
            
            serviceProps.load(
                Files.newBufferedReader(
                    Paths.get("$projectDir/dist/config/$plat/$it/config.properties"),
                    StandardCharsets.UTF_8
                )
            )
            
            log4j2Props.load(
                Files.newBufferedReader(
                    Paths.get("$projectDir/dist/config/$plat/$it/log4j2.xml.properties"),
                    StandardCharsets.UTF_8
                )
            )
            
            envSpecificServicePropsXslTransformParameters[plat][it] = serviceProps
            envSpecificLog4j2XslTransformParameters[plat][it] = log4j2Props
        }
    }
    
    def genTasks = { p ->
        def previousLog4j2Task = null
        def previousServicePropsTask = null
        
        deploymentPlatforms.each {
            def deploymentPlatform = it
            
            deploymentEnvironments.each {
                def deploymentEnv = it
                def propsTaskName = "xslt_${deploymentPlatform}_service_props_$deploymentEnv"
                p.logger.info("Creating XSL Transform task: $propsTaskName")
                
                def newServicePropsXslTask = p.tasks.create(
                    [name: propsTaskName, type: SaxonXsltTask], {
                        input "${p.projectDir}/dist/config/$deploymentPlatform/config.properties.xml"
                        stylesheet "${p.projectDir}/transforms/build/apply-env-specific-service-properties.xsl"
                        output "${p.projectDir}/config/$deploymentPlatform/$deploymentEnv/config.properties"
                        parameters(envSpecificServicePropsXslTransformParameters[deploymentPlatform][deploymentEnv])
                    }
                )
    
                def log4j2XmlTaskName = "xslt_${deploymentPlatform}_service_log4j2_$deploymentEnv"
                p.logger.info("Creating XSL Transform task: $log4j2XmlTaskName")
    
                def newLog4j2XslTask = p.tasks.create(
                    [name: log4j2XmlTaskName, type: SaxonXsltTask], {
                        input "${p.projectDir}/dist/config/$deploymentPlatform/log4j2.xml"
                        stylesheet "${p.projectDir}/transforms/build/apply-env-specific-service-logging.xsl"
                        output "${p.projectDir}/config/$deploymentPlatform/$deploymentEnv/log4j2.xml"
                        parameters(envSpecificLog4j2XslTransformParameters[deploymentPlatform][deploymentEnv])
                    }
                )
                
                if (previousServicePropsTask != null) {
                    newServicePropsXslTask.dependsOn(previousServicePropsTask)
                }
                if (previousLog4j2Task != null) {
                    newLog4j2XslTask.dependsOn(previousLog4j2Task)
                }
                
                previousServicePropsTask = newServicePropsXslTask
                previousLog4j2Task = newLog4j2XslTask
            }
        }
        
        return [
            previousServicePropsTask: previousServicePropsTask,
            previousLog4j2Task: previousLog4j2Task
        ]
    }
    
    def cleanupCustomTask = { p ->
        def configsForRemoval = [:]
        def dirsForRemoval = [:]
        
        deploymentPlatforms.each {
            def deploymentPlatform = it
            def generatedPlatformDir = "${p.projectDir}/config/$deploymentPlatform"
            
            deploymentEnvironments.each {
                def deploymentEnv = it
                def distributionName = "$deploymentPlatform${deploymentEnv.capitalize()}"
                def generatedConfigDir = "$generatedPlatformDir/$deploymentEnv"
                def generatedServicePropsConfig = "$generatedConfigDir/config.properties"
                def generatedLog4j2Config = "$generatedConfigDir/lo4j2.xml"
                def generatedDistNameDir = "${p.projectDir}/src/$distributionName"
                def generatedConfigVersionDistDir = "$generatedDistNameDir/dist"
                def generatedConfigVersionText = "$generatedConfigVersionDistDir/config-version.txt"
                def generatedOldConfigVersionText = "$generatedConfigVersionDistDir/version.txt"
                
                configsForRemoval[generatedServicePropsConfig] = Files.exists(Paths.get(generatedServicePropsConfig))
                configsForRemoval[generatedLog4j2Config] = Files.exists(Paths.get(generatedLog4j2Config))
                configsForRemoval[generatedConfigVersionText] = Files.exists(Paths.get(generatedConfigVersionText))
                configsForRemoval[generatedOldConfigVersionText] = Files.exists(Paths.get(generatedOldConfigVersionText))
                
                dirsForRemoval[generatedConfigDir] = Files.exists(Paths.get(generatedConfigDir))
                dirsForRemoval[generatedConfigVersionDistDir] = Files.exists(Paths.get(generatedConfigVersionDistDir))
                dirsForRemoval[generatedDistNameDir] = Files.exists(Paths.get(generatedDistNameDir))
            }
            
            dirsForRemoval[generatedPlatformDir] = Files.exists(Paths.get(generatedPlatformDir))
        }
        
        //Also handle mainConfig, which is not dynamic.
        def mainConfigDistNameDir = "${p.projectDir}/src/mainConfig"
        def mainConfigVersionDistDir = "$mainConfigDistNameDir/dist"
        def mainConfigVersionText = "$mainConfigVersionDistDir/config-version.txt"
        def mainConfigOldVersionText = "$mainConfigVersionDistDir/version.txt"
        
        configsForRemoval[mainConfigVersionText] = Files.exists(Paths.get(mainConfigVersionText))
        configsForRemoval[mainConfigOldVersionText] = Files.exists(Paths.get(mainConfigOldVersionText))
        
        dirsForRemoval[mainConfigVersionDistDir] = Files.exists(Paths.get(mainConfigVersionDistDir))
        dirsForRemoval[mainConfigDistNameDir] = Files.exists(Paths.get(mainConfigDistNameDir))
        
        return [
            configsForRemoval: configsForRemoval,
            dirsForRemoval: dirsForRemoval
        ]
    }
    
    def genConfigDistTasks = { p, appName ->
        deploymentPlatforms.each {
            def deploymentPlatform = it
            
            deploymentEnvironments.each {
                def deploymentEnv = it
                def distributionName = "$deploymentPlatform${deploymentEnv.capitalize()}"
                
                p.distributions.create(distributionName, {
                    baseName = appName
                    
                    contents {
                        into('config') {
                            from fileTree('src/main/resources').matching {
                                        exclude 'spotbugs-exclusion-filters.xml',
                                            'config/config.properties',
                                            'config/log4j2.xml'
                                    }.files
                            from fileTree("config/$deploymentPlatform/$deploymentEnv/log4j2.xml").
                                    files
                        }
                    }
                })
                
                p.tasks.getByName("${distributionName}DistZip") {
                    preserveFileTimestamps = false
                    reproducibleFileOrder = true
                    archiveBaseName = appName
                    includeEmptyDirs = true
                    
                    dependsOn p.tasks.transformConfigurations
                    
                    def appVersion = null
                    doFirst {
                        appVersion = p.jar.ext.has('version') ? p.jar.ext.version : {
                                apply from: "${p.projectDir}/gradle/version-util.gradle"
                                return getVersionInfo().full
                            }()
                        
                        def distDir = "${p.projectDir}/src/$distributionName/dist"
                        
                        if (Files.notExists(Paths.get(distDir))) {
                            Files.createDirectories(Paths.get(distDir))
                        }
                        
                        Files.write(Paths.get("${p.projectDir}/src/$distributionName/dist/config-version.txt"),
                                "$appName-config-$appVersion".toString().getBytes())
                    }
                    
                    doLast {
                        def distAppName = "${p.properties['application.configTitle']}-${appVersion}" +
                                ".${distributionName}.config.${archiveExtension.get()}"
                        
                        p.logger.info(
                          "Fixing archive name, renaming from ${archiveFileName.get()} to $distAppName"
                        )
                        
                        def zabd = "build/distributions"
                        file("$zabd/${archiveFileName.get()}").
                          renameTo(file("$zabd/$distAppName")
                        )
                    }
                }
            }
        }
    }
    
    ext {
        //Add closures to extension object
        generateConfigurationDistributionTasks = genConfigDistTasks
        cleanUpCustomTrash = cleanupCustomTask
        generateTransformTasks = genTasks
    }