gradle的WriteProperties任务只有在直接运行任务时才会创建属性文件。

huangapple go评论69阅读模式
英文:

gradle WriteProperties task does not create properties file unless the task is ran directly

问题

我正在尝试在一个非Android的Java项目中生成属性文件,但出于某些原因,即使在使用./gradlew assembleDist构建项目或使用./gradlew foo:run运行项目时任务会运行,属性文件也没有被创建。该任务在子项目中定义,代码如下:

task writeProperties(type: WriteProperties) {
    outputFile 'src/main/resources/foo.properties'
    property 'version', version
    property 'releaseType', releaseType
    property 'date', date
    println("task ran")
}

当我执行其他Gradle任务时,会打印出task ran,表明任务已运行,但在指定路径中并没有出现foo.properties文件。奇怪的是,如果我直接使用./gradlew foo:writeProperties运行writeProperties任务,文件会被创建并且正确的变量值会被写入。

我尝试过用doFirst{}doLast{}来包装上述语句,但会出现类似以下的错误:

Cause: path may not be null or empty string. path='null'

有人知道我可能做错了什么吗?我已经花了几个小时在这个问题上,想法已经用尽。谢谢!

更新: 我根据接受的答案提供的信息解决了这个问题。我必须添加compileJava.finalizedBy(writeProperties)以使任务实际上作为构建任务运行。正如最高票答案提到的,println本质上是在配置阶段输出的,但任务的实际操作并没有运行。

英文:

I am trying to generate properties files in a non-Android Java project, but for some reason even though the task runs when I build the project with ./gradlew assembleDist or run it with ./gradlew foo:run, the properties file isn't created. The task is defined in a subproject and looks like this:

task writeProperties(type: WriteProperties) {
    outputFile 'src/main/resources/foo.properties'
    property 'version', version
    property 'releaseType', releaseType
    property 'date', date
    println("task ran")
}

task ran gets printed when I execute the other gradle tasks, indicating that the task ran, but no foo.properties file shows up in the specified path. Weirdly enough, if I run the writeProperties task directly with ./gradlew foo:writeProperties, the file gets created and populated with the correct variables.

I've tried wrapping the statements above with doFirst{} and doLast{} but I get errors like the following:

Cause: path may not be null or empty string. path='null'

Does anyone have any idea what I could be doing wrong? I've been banging my head against this for hours and am running out of ideas. Thanks!

update: I managed to solve it with information provided in the accepted answer. I had to add compileJava.finalizedBy(writeProperties) to get the task to actually run as part of the build tasks. As the top answer mentioned, println was essentially lying to me, as that was being printed during the configuration phase but the task action was not being ran.

答案1

得分: 0

你代码中的实际问题是你的println语句没有说实话。任务配置闭包中的代码在任务被配置时运行,而不是在执行时运行。因此,每次你从命令行运行另一个任务时,都会配置你的任务,并且println会写入到命令行,但实际的任务动作不会被执行。你应该将你的println放到doLast中,这样它实际上会在.properties文件被创建后运行。

task myTask {
    println '任务已配置'
    doLast {
        println '任务已执行'
    }
}

如果你希望任务在不在命令行指定的情况下运行,你需要使用dependsOnfinalizedBy来设置任务依赖关系。

英文:

The actual problem with your code is that your println statement is not telling the truth. The code in the task configuration closure runs when the task gets configured, not when it gets executed. So everytime you run another task from the command line, your task gets configured and the println writes to the command line, but the actual task action is not executed. You should pack your println into a doLast, so that it actually runs after the .properties file has been created.

task myTask {
    println 'task configured'
    doLast {
        println 'task executed' 
    } 
} 

If you want your task to run without specifying it on the command line, you need to setup task dependencies using dependsOn or finalizedBy.

答案2

得分: 0

所描述的问题并不是一个问题。正在发生的情况恰好是 Gradle 的工作方式。我也曾经努力理解为什么这种行为是正确的!我提供一个答案,希望能对正在发生的事情有些许的明确性。Algebo 并不是唯一一个试图理解 Gradle 工作原理的人。Algebo 在描述他所观察到的情况时做得非常好,希望我的答案能解开其中的一些神秘感。

首先要理解的一件事是,Gradle 在三个阶段中评估代码:初始化、配置和执行。

在初始化阶段,Gradle 确定了哪些项目要包含在构建中。

在配置阶段,任务被创建、配置并添加到构建图中。当配置任务时,你需要告诉 Gradle 任务的输入和输出是什么。Gradle 使用这些信息来判断是否需要实际执行任务。(例如,如果任务的输入和输出是最新的,那么可以跳过任务的执行。)

在执行阶段,Gradle 执行与任务相关联的操作。

通过以上的解释,让我们看看 Algebo 代码中发生了什么。Algebo 首先注意到的是他看到了“任务运行”消息。那么为什么会这样呢?因为该代码是在 Gradle 配置阶段执行的。(Lukas 的回答在解释这一观察时做得很好)因此,此时所做的一切只是配置了“writeProperties”任务对象(其类型为“WriteProperties”)。当然,Algebo 正确地注意到“foo.properties”文件未被创建。当然,这是因为任务只是被配置了,并未被执行。

接下来,Algebo 尝试将代码放入任务块中的 doFirst 和 doLast 块中。当 Algebo 尝试这样做时,他会收到路径可能为空或空白的错误消息。doFirst 和 doLast 是定义任务操作的代码块。正如我之前所述,任务操作会在任务执行阶段执行。那么为什么 Algebo 会收到这个错误呢?对于“WriteProperties”任务而言,它要求“outputFile”要配置好。但是,Algebo 将 outputFile 语句移到了任务操作块中。换句话说,在执行 doFirst 或 doLast 代码时,outputFile 还没有被定义。在任务操作被执行之前,“WriteProperties” 任务需要先定义这个。这就解释了观察到的行为。

那么,为什么当他直接使用 ./gradlew foo:writeProperties 执行任务时可以工作呢?那是因为任务被正确配置,当他直接执行它时,它会执行与任务相关的操作。然而,Algebo 并没有定义任何任务操作,因此没有操作会被执行,一旦任务操作完成(没有操作可执行),“WriteProperties” 任务会通过将定义的属性写入 outputFile 来结束。这正是 Algebo 观察到的情况。

最后,Algebo 注意到他可以通过添加语句:compileJava.finalizedBy(writeProperties) 来使任务实现他想要的效果。在这种情况下,“WriteProperties” 任务已经被配置好了。此外,在配置阶段,该任务被添加到构建图中。换句话说,在“compileJava”任务完成时,该任务被配置为运行。当执行“writeProperties”任务时,它已经被正确配置,因此会执行任务的操作(在这种情况下没有操作)。操作完成后(没有操作可执行),“WriteProperties” 任务通过将属性写入 “outputFile” 来结束。

尽管以上已经说得很清楚,但是“writeProperties” 任务还存在另一个问题。Gradle 现在要求用户定义所有任务的输入和输出。Gradle 使用这些信息来确定是否需要执行任务,或者如果任务已经是最新的则跳过执行。在 Algebo 的示例中,输入是:“version”、“releaseType” 和 “date”,而输出是文件 “src/main/resources/foo.properties”。(注意:Gradle 并不知道 “outputFile” 是 “outputs” 的一部分)。“writeProperties” 任务没有配置它的输入和输出。因此,“writeProperties” 将始终被执行,即使自上次执行以来没有任何变化。在 Algebo 的最终解决方案中,如果例如 “version” 更改且 “compileJava” 任务已经是最新的,那么他的属性文件将不会重新构建。

关于额外的信息,我发现这个链接很有帮助。

英文:

The described issue is not a problem. What's happening is exactly how Gradle works. I too have struggled with understanding why this behavior is correct! I'm providing an answer to help bring some clarity as to what's going on. Algebo is not alone in trying to understand how Gradle works. Algebo did a great job in describing what he observed and hopefully my answer takes out some of the mystery of what's going on.

First thing to understand is that gradle evaluates the code in three passes: initialization, configuration and execution.

During initialization, gradle figures out which projects are to be included in the build.

During configuration, the tasks are created and configured and added to the build graph. When a task is configured, you need to tell gradle what are the inputs and outputs to the task. Gradle uses that information to know whether it actually needs to execute the task or not. (i.e. if the inputs and outputs of a task is up-to-date, then you can skip the execution of the task)

During execution, gradle then executes the actions associated with task.

With the above explanation, let's look at what's happening with Algebo's code. The first thing that Algebo notices is that he sees the "task ran" message. So why was that? That's because that code was executed during the gradle configuration phase. (Lukas answer does a good job explaining this same observation) So, all that's done at this time is that the "writeProperties" task object (which is of type "WriteProperties") get configured. And of course, Algrebo correctly notices that the "foo.properties" file was not created. And that of course is because the task was only configured and not yet execute.

The next thing Algebo tries is to put the code in the task block in a doFirst and a doLast block. When Algebo tries that, he gets the error message that the path may be null or empty. The doFirst and doLast the blocks of code that define a task action. As I stated earlier, it the task actions that get executed during the task execution pass. So why does Algebo get that error. In the case of the "WriteProperties" task, it requires that the "outputFile" to be configured. But, Algebo moved the outputFile statement to a task action block. In other words, the outputFile has not been defined at the time either the doFirst or doLast code gets executed. The "WriteProperties" task needs that define prior to the task actions being performed. So that explains that observed behavior.

So why does it work when he executes the task directly using ./gradlew foo:writeProperties directly. Well, that's because the task is properly configured, and when he executes it directly, it executes the task actions associated with the task. Well, Algebo didn't define any task actions, so there's none to be executed, and once the task actions have completed (none), the WriteProperies task concludes by writing of the defined properties to the outputFile. And that's of course what Algebo observed.

And lastly, Algebo noticed that he could get the task to do what he wanted to by adding the statement: compileJava.finalizedBy(writeProperties). In that case, the WiteProperties task has been configured. Additionally during the configuration pass, that task was added to the build graph. In other words, that task was configured to be run when the "compileJava" task completed. When the "writeProperties" task is executed, it has been properly configured, so it executes the task actions for the task (none in this case) and when done the "WriteProperties" task concludes by writing the properties to the "outputFile".

With all the above said, there's still another issue with the "writeProperties" task as defined. Gradle now wants user to define the inputs and outputs of all tasks. Gradle uses that imformation to determine whether a task needs to be executed or skipped if it already up-to-date. In Algebo's example, the inputs are: "version", "releaseType" and "date" and the outputs are the file "src/main/resources/foo.properties". (Note: gradle doesn't know that "outputFile" is part of "outputs"). The "writeProperties" task doesn't have its inputs and outputs configured. So "writeProperties" will always get executed even if nothing has changed since it was last executed. In Algebo final working solution, his properties file would not get rebuilt if for example "version" changed and the compileJava task was already up-to-date.

For additional information, I found this to be helpful.

答案3

得分: -1

1. 在构建文件夹中生成新代码(不是主要源代码)
2. 将生成的文件添加到源代码集合中
3. 将新任务链接到构建链中

ext.genRes = 'build/generated_src/main/resources'
sourceSets {
    main {
        resources {
            srcDir(genRes)
        }
    }
}

ext.genResFoo = genRes + 'foo.properties'
ext.releaseType = 'test'

task genResources(type: WriteProperties) {
    outputFile genResFoo
    property 'version', version
    property 'releaseType', releaseType
    property 'date', new java.util.Date()
    doLast {
        println("writeProperties运行于${genResFoo}")
    }
}
processResources.dependsOn genResources
英文:
  1. generate new code in the build folder (not main source)

  2. add the generated files to the source set

  3. link new task to the build chain

    ext.genRes = 'build/generated_src/main/resources'
    sourceSets {
    main {
    resources {
    srcDir(genRes)
    }
    }
    }

    ext.genResFoo = genRes + 'foo.properties'
    ext.releaseType = 'test'

    task genResources(type: WriteProperties) {
    outputFile genResFoo
    property 'version', version
    property 'releaseType', releaseType
    property 'date', new java.util.Date()
    doLast {
    println("writeProperties ran ${genResFoo}")
    }
    }
    processResources.dependsOn genResources

huangapple
  • 本文由 发表于 2020年9月19日 07:42:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/63963999.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定