stevesaliman / gradle-properties-plugin

Gradle plugin to simplify loading project properties from external environment specific files
Apache License 2.0
192 stars 28 forks source link

Overwriting Files When Switching Environments #20

Closed viktornordling closed 9 years ago

viktornordling commented 9 years ago

Hi,

Thanks for creating this plugin, it looks awesome so far!

One question, if I switch from one profile to another, my generated files aren't overwritten. Is this expected or is there a way to override this behaviour?

Cheers!

//V

stevesaliman commented 9 years ago

The plugin was only designed to add new files to the Gradle property processing mechanism, it doesn't do anything more than that. What I do in my projects is create a task named prep that copies my template files into the resource directory. I then make compileJava depend on prep. This way, I always get fresh copies of any files that would be affected by the environment I'm using.

If I was generating files, and that step was expensive timewise, I'd simply skip the compileJava dependency and make sure to run it manually whenever I needed to build for a different environment.

viktornordling commented 9 years ago

Thanks Steve,

But does that mean you can't switch environment without manually deleting the files first?

//V

asaarnak commented 9 years ago

1.When i run copy task using filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: project.filterTokens) 2.Copied files are filtered and values like @myProperty@ are overwrited. 3.Change the environmentName and run the task again. 4.The task is UP-TO-DATE and filter values are same as in point 2.!

Related to bug: https://issues.gradle.org/browse/GRADLE-1646

stevesaliman commented 9 years ago

@viktornordling I've made my compileJava task depend on prep. The prep task is never up to date, so it copies files every time I do a build, so you can safely switch environments at will.

However, I was thinking about this recently, and I realize that this would in turn mean that compileJava would never be up to date, and this means a lot of unnecessary compilation. In our case this is not a big deal, but for larger projects this would be an issue. The solution to this might be to create a prep task that is smarter about when things have changed. I don't think this is a plugin issue since it doesn't know how properties will be used, but if I find a smarter way to create the prep task, I'll update this issue with the solution.

stevesaliman commented 9 years ago

@asaarnak I do my copies with a separate prep task, which is never up to date, so I always get the right values. This can slow down the build, but it would be accurate.

I did notice that you used project.filterTokens. My task is using project.ext.filterTokens I would doubt that this would make any difference, but it might be worth trying...

stevesaliman commented 9 years ago

I've made some enhancements to 1.4.3-SNAPSHOT that might help out with these issues.

The plugin creates 4 methods in every task that can be used to enforce the presense of properties at run time. I've enhanced these methods to register the properties as task inputs.

For example, consider the following task:

task prep() {
  description = 'Copy resources and files needed for building'
  requiredProperties 'appLogLevel', 'defaultLogLevel', 'logFilePrefix'
  outputs.file new File(resourceDir, 'log4j.properties')
  doFirst {
    copy {
      from templateDir
      include 'log4j.properties'
      into resourceDir
      filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: project.ext.filterTokens)
    }
  }
}

This task will fail if we don't define values for the 'appLogLevel', 'defaultLogLevel', and 'logFilePrefix' properties. If the properties are present, the task will copy a log4j.properties file, making token substitutions along the way. The task registers the log4j.properties file as a task output and the plugin now registers the 3 properties as task inputs, so if you run the prep task twice, it will be considered up to date. If the value for any of the 3 properties should change, the task will no longer be considered up to date and will make a new copy of the log4j.properties file.

You can include the latest SNAPSHOT release of the plugin by adding the Sonatype Snapshot repo to your build.gradle. I'd be interested to know how this works for you before I release this to the general public.

viktornordling commented 9 years ago

I solved the issue thanks to http://stackoverflow.com/questions/16154042/gradle-force-custom-task-to-always-run-no-cache.

The trick is to add: prep.outputs.upToDateWhen { false }

Not sure why your prep task is never up to date Steve?

stevesaliman commented 9 years ago

I looked into this over the weekend. My prep task was never up to date because it didn't declare any inputs or outputs. I've enhanced the plugin to declare inputs, and the task from my last comment declares outputs, so the up to date checking now works as I would expect. upToDateWhen {false} works if you want to force the file copy every time.

viktornordling commented 9 years ago

Thanks Steve,

That's cool that your task detects changes now. Wouldn't there be an issue though were the files actually don't change, only the environment does? So if I change from dev to prod, no input files have changed on disk, just the flag, if that makes sense?

Regardless, would you care to share what your updated task looks like?

I also still don't understand why previously your task was never up-to-date, when for me and asaarnak the task is always up-to-date?

stevesaliman commented 9 years ago

My original prep task didn't declare any outputs, and I found a bit in the Gradle documentation that said that a task that doesn't declare any outputs is never up to date.

In addition to outputs, a task uses inputs to determine if anything changed that effects the task. My original prep task didn't declare any inputs either, further adding to my problem.

I made a change to the plugin to declare as inputs any property mentioned in the requiredProperties method (and the 3 others from the plugin documentation), and I added an output to the task. My updated task is the one in the comment 4 posts up. The important bits are requiredProperties 'appLogLevel', 'defaultLogLevel', 'logFilePrefix', which tells the plugin that 3 properties are required, and that they should be inputs to the task, and outputs.file new File(resourceDir, 'log4j.properties') which tells the task that it is producing a log4j.properties file.

Since the task now watches the 3 required properties, if any one of them changes for any reason, the task will no longer be up to date. So if you change environments, the task re-runs.

If you use the snapshot version of the plugin, you could try it out. I'd be curious as to how it works for you before doing an official release.

I'm not sure why asaarnak's task was always up to date without seeing it, but I was under the impression that he was copying files as part of another task that had its own inputs and outputs that caused it to be up to date, but that those inputs and outputs had nothing to do with the properties or the files he was copying. That would cause the task to always be up to date.

viktornordling commented 9 years ago

Thanks Steve,

It's still not working the way you describe for me. If I remove the prep.outputs.upToDateWhen { false } line my task is always up-to-date, despite declaring requiredProperties. Perhaps we are on different versions of Gradle and this behaviour has changed in between? I am currently on 1.11..

viktornordling commented 9 years ago

Ah, I see now, the task.inputs.property(propertyName, propertyValue) was only added in the SNAPSHOT version, so that's why it's not working for me. Unfortunately I can't test with this version because of some incompatibilities between versions. I first got an error due to the Groovy version, and when I fixed that I got this rather cryptic error:

10:22:03.405 [ERROR] [org.gradle.BuildExceptionReporter] Caused by: java.lang.IncompatibleClassChangeError: the number of constructors during runtime and compile time for net.saliman.gradle.plugin.properties.FileType do not match. Expected -1 but got 2
10:22:03.406 [ERROR] [org.gradle.BuildExceptionReporter]    at net.saliman.gradle.plugin.properties.FileType.$INIT(FileType.groovy)
10:22:03.406 [ERROR] [org.gradle.BuildExceptionReporter]    at net.saliman.gradle.plugin.properties.FileType.<clinit>(FileType.groovy)
10:22:03.407 [ERROR] [org.gradle.BuildExceptionReporter]    at net.saliman.gradle.plugin.properties.PropertiesPlugin.buildPropertyFileListFromProject(PropertiesPlugin.groovy:206)
10:22:03.407 [ERROR] [org.gradle.BuildExceptionReporter]    at net.saliman.gradle.plugin.properties.PropertiesPlugin.this$2$buildPropertyFileListFromProject(PropertiesPlugin.groovy)

Anyway, I can make it work manually by adding task.inputs.property calls. Thanks.

stevesaliman commented 9 years ago

That is a strange error. I'm not sure what it is, but I added a sourceCompatibility and targetCompatibility of 1.6 to rule out JVM issues and did a clean and build to make sure all class files were recompiled. Does the snapshot work now?

viktornordling commented 9 years ago

Did you forget to push?

stevesaliman commented 9 years ago

I pushed to Maven's snapshot repo, but it sometimes takes a bit to show up. I just pushed the build.gradle changes in case you were building the artifact yourself. Does it work now?

stevesaliman commented 9 years ago

I just released version 1.4.3 to production, and it is now available on the Gradle plugin portal. It should be on Maven Central shortly.