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

时间:2018-12-12 08:59:23

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

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

这是我的子脚本(IDE现在没有抱怨)

<select class="bootstrap-select show-tick" data-size="5" data-live-search="true" data-width="100%" name="TName1" required>
    <option value="" disabled selected>Select Sub Account Name</option>
    <?php foreach ($accName as $row ):
    $selected = ($dbValueForDropDown == $row['id']) ? 'selected="selected"' : '';
    ?> 
    <option value="<?=$row['id']?>" <?php echo set_select('accName', $row['id']); ?> <?php echo $selected;?>><?=$row['accName']?></option> 
    <?php endforeach ?>         
</select>

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

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")

如何解决此问题,以便可以在子脚本中应用例如detekt插件,并将其应用到父脚本中?

3 个答案:

答案 0 :(得分:0)

From a Gradle core dev(情况与您的情况非常相似):

  

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

     

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

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

答案 1 :(得分:0)

我尝试了很多方法。总之,在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<PluginClass>正确地位于这些块中,因为这似乎也不适合上述插件问题),方法是在应用脚本中声明多余的lamb,然后从allProjects内的目标脚本中调用它们,或者与它们相关的subProjects个配置块。

例如:

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
  • 大力考虑退回部队,以提供无副作用的使用。一些插件具有扩展名,这些扩展名将通过简单地注册在它们的config块中找到的所有实例来自动配置容器的各种元素。除非绝对必要,否则不隐式支持链接是最安全的选择。
  • 请记住,在许多情况下,此处不能依赖于标准库中的kotlin的T.apply { },因为它将解析为Gradle的apply,因此如果您要这样做(或返回到单元)可以轻松地跨多个配置lambda,最好创建一个小的辅助函数,该函数基本上可以完成Kotlin中所应用的工作,或者像我所做的那样,构建一个助手,该助手运行给出的块并始终返回Unit(或某些泛型)类型)。

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

答案 2 :(得分:0)

我的回答建立在模糊武器的回答之上,因为我在看到它之前无法弄清楚,恭喜!

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

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

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

  1. 将逻辑移到一个单独的文件中,在我的例子中,我在 $projectDir/gradle/env-config.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
}

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