【问题标题】:Gradle not recognizing plugin tasks from applied child scriptGradle 无法从应用的子脚本中识别插件任务
【发布时间】:2018-12-12 08:59:22
【问题描述】:

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

这是我的子脚本(IDE 目前没有抱怨)

import io.gitlab.arturbosch.detekt.Detekt


task("hello").doLast {
    println("Hello World from plugins! :D")
}


tasks {
    val detekt by existing(Detekt::class) {
        reports {
            html {
                val destination = "$buildDir/detekt"
            }
        }
    }
}

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

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 插件并将其应用于父脚本?

【问题讨论】:

  • 你能分享你的buildscript 块吗?
  • @ToYonos:我没有,因为插件 dsl 没有必要?

标签: gradle build.gradle gradle-kotlin-dsl


【解决方案1】:

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

未经测试:我会尝试的方法(这是我还没有尝试过的一件事)是在应用的脚本中同时应用插件(旧式)和配置 all插件作为显式跨项目配置,不需要在您应用的脚本中使用 allProjectssubProjects 块。

你应该参考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...
    }
}

这是因为在 Kotlin DSL 中,allProjectssubProjects 块在应用脚本插件中定义时仍然不像人们希望/期望的那样(据我所知)。

尽管我的要求使我不值得测试上面的完整跨项目/应用插件技术,但我确实绕过了 allProjectssubProjects 的行为不符合预期(并且需要正确使用 withPlugin&lt;PluginClass&gt;在这些块中,因为这似乎也不适用于上面的插件问题)通过在我应用的脚本中声明 extra-lambas ,然后从它们相关的allProjectssubProjects 配置块中的目标脚本调用它们为。

例如:

applied.build.gradle.kts

val allProjectsConfiguration by extra { p: Project ->
    // Some project configuration here...
}

build.gradle.kts

//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.

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

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

@ToYonos - gradle 建议不适用于 Kotlin DSL。我的结果很差(我专门使用了classpath 依赖项),因为 gradle 会报告该插件从未被应用,并且在同一条错误消息中它显示应用了扩展。我的浅薄猜测是这与字节码和/或为应用脚本具有单独的类加载器有关。 (对不起,我会在您的答案的评论中对此发表评论,但没有足够的代表)

【讨论】:

    【解决方案2】:

    From a Gradle core dev(情况和你的差不多):

    脚本插件无权访问主构建脚本类路径。

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

    所以基本上,使用旧的buildscript 语法,你的导入应该可以工作

    【讨论】:

    • 我会在接下来的几天里尝试一下。感谢您在论坛上引用该问题。 :)
    【解决方案3】:

    我的答案是建立在模糊武器的答案之上的,因为我在看到它之前无法弄清楚,荣誉!

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

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

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

    1. 将逻辑移动到单独的文件中,在我的例子中,我在我的项目中将其命名为 env-config.gradle $projectDir/gradle/env-config.gradle
    2. 将其包装在参数化闭包中。
    3. 修复您 (OP) 在您的问题中提出的错误。大多数这些问题都可以使用直接导入来解决,就像您在 Java、Kotlin 或直接在 Groovy 中所做的那样。这可能需要构建脚本依赖项,特别是对于二进制插件,您将在下面的完整示例中看到。
    4. 将闭包引用附加到扩展脚本插件对象。
    5. 使用项目对象作为参数从您提取逻辑的构建脚本中调用闭包。

    这是一个基于我为解决该问题所做的简化示例,我在其中定义任务创建和任务逻辑,然后外部化对闭包的引用,以便可以从应用了该脚本的脚本中调用它:

    $projectDir/gradle/env-config.gradle:

    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
    }
    

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

    $projectDir/build.gradle:

    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
    }
    

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

    【讨论】:

      猜你喜欢
      • 2018-10-28
      • 1970-01-01
      • 2015-12-10
      • 2018-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      相关资源
      最近更新 更多