I'm trying to utilize jOOQ's ability to generate from Liquibase files. My file structure is as follows:
C
- dev
-- testproject
--- src/main/resources
---- db
----- changelog.xml
In order to reference this file from the jOOQ configuration, I have the following in my build.gradle.kts:
jooq {
configurations {
create("main") {
jooqConfiguration.apply {
generator.apply {
database.apply {
name = "org.jooq.meta.extensions.liquibase.LiquibaseDatabase"
properties.add(Property().apply {
key = "rootPath"
value = "C:/dev/testproject/src/main/resources/db/"
})
properties.add(Property().apply {
key = "scripts"
value = "changelog.xml"
})
}
}
}
}
}
}
I'm using plugin version 7.1.1 and have the following dependencies:
dependencies {
implementation("org.liquibase:liquibase-core:4.8.0") // I tried removing this, no change
jooqGenerator("org.postgresql:postgresql:42.3.2")
jooqGenerator("org.jooq:jooq-meta-extensions-liquibase:3.17.2")
jooqGenerator(files("src/main/resources")) // I don't think this is necessary
}
When I try to run jooqGenerate, the error I get is:
Caused by: liquibase.exception.ChangeLogParseException: The file changelog.xml was not found in
Specifying files by absolute path was removed in Liquibase 4.0. Please use a relative path or add '/' to the classpath parameter.
at liquibase.parser.core.xml.XMLChangeLogSAXParser.parseToNode(XMLChangeLogSAXParser.java:82)
at liquibase.parser.core.xml.AbstractChangeLogParser.parse(AbstractChangeLogParser.java:15)
at liquibase.Liquibase.getDatabaseChangeLog(Liquibase.java:369)
Notice how it doesn't say which directories it looked in. As far as I can tell, the resource accessor is not receiving the rootPath from the configuration. The relevant output from Liquibase is here. Again, it should say it looked in the rootPath, but it doesn't print anything else, so there must be no directories searched.
Not sure if this is helpful, but the jOOQ configuration file in build/tmp/generateJooq definitely has the rootPath:
<property>
<key>rootPath</key>
<value>C:/dev/testproject/src/main/resources/db/</value>
</property>
I'm not sure where I'm going wrong. I've also tried the following values of scripts without setting rootPath and seen the same behavior:
C:/dev/testproject/src/main/resources/db/changelog.xml
src/main/resources/db/changelog.xml
/src/main/resources/db/changelog.xml
classpath:src/main/resources/db/changelog.xml
classpath:/src/main/resources/db/changelog.xml
This was causing the problem (or rather, the confusion):
jooqGenerator(files("src/main/resources"))
Apparently, this sets the classpath of the jooqGenerator task to be src/main/resources! So, knowing that, I fixed my configuration to look like this:
database.apply {
name = "org.jooq.meta.extensions.liquibase.LiquibaseDatabase"
properties.add(Property().apply {
key = "scripts"
value = "classpath:db/changelog.xml"
})
}
Everything is working nicely now.
Related
We are filtering an xml file replacing some tokens with gradle properties.
But the filtering (i.e. copy task) is not executed when we just change the properties in our build.gradle file.
How should we modify our script so that the filtering is performed each time or at least when the template and/or the build.gradle has been modified.
This we have:
war.doFirst {
delete 'src/main/webapp/WEB-INF/appengine-web.xml'
copy {
from 'build.gradle'
from 'src/main/webapp/WEB-INF/'
into 'src/main/webapp/WEB-INF/'
include '*-template.*'
rename { String fileName ->
fileName.replace('-template', '')
}
expand(gaeApp: "$gaeApp", gaeAppVersion: "$gaeAppVersion")
}
}
I just ran some test where the filtering worked. I am confused... I am sure that it sometimes does not!
So after good input from Vampire we tried this
war {
inputs.file "build.gradle"
exclude 'src/main/webapp/WEB-INF/appengine-web.xml'
// filesMatching('src/main/webapp/WEB-INF/**/*-template.*') {
filesMatching('**/*-template.*') {
println "WAR template: $it"
rename { it.replace '-template', '' }
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
A dollar and a dime to anyone who can explain why the filesMatching('src/main/webapp/WEB-INF/**/*-template.*')does not work!
BUT the biggest problem is that even if the filesMatching locates the template file the appengine-web.xml that is placed inside the WAR is not a processed version of appengine-web-template.xml.
You need to add those properties to the inputs of the task like
war.inputs.property 'gaeApp', gaeApp
war.inputs.property 'gaeAppVersion', gaeAppVersion
so that gradle knows the input changed, otherwise it cannot know when the input is different.
But besides that, you should not (need not) use a copy { } block in there.
The war task itself is an implicit copy spec, so you should be able just doing something like
war {
inputs.property 'gaeApp', gaeApp
inputs.property 'gaeAppVersion', gaeAppVersion
exclude 'src/main/webapp/WEB-INF/appengine-web.xml'
filesMatching('src/main/webapp/WEB-INF/**/*-template.*') {
rename { it.replace '-template', '' }
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
This is what worked for us in the end.
We moved the template to 'src/template/webapp' and removed the "-template" suffix,
war {
inputs.file "build.gradle"
with copySpec {
from 'src/template/webapp'
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
Our problem with Vampire's solution must be related to the fact that the template file was in same directory as the file it was to replace.
Is there a way to add empty directories (e.g, "logs") when creating a distribution with the gradle distribution plugin?
I saw this JIRA, describing the exact same thing. It is still open https://issues.gradle.org/browse/GRADLE-1671
I wonder if there are any workarounds I can use. I don't quite understand the workarounds described in the jira.
Thank you.
So I managed to work around this by following the suggestion in the mentioned JIRA to create a dummy empty directory and then copy it to the distribution location.
It's ugly but works. I'm sure it can be written more efficiently though. This is the Copy block from inside distributions/main/contents:
into('') {
//create an empty 'logs' directory in distribution root
def logDirBase = new File('/tmp/app-dummy-dir')
logDirBase.mkdirs()
def logDir = new File(logDirBase.absolutePath + '/logs')
logDir.mkdirs()
from {logDirBase}
}
Based on Logato's own answer I've come up with the following code, which is more elegant and also closes the file pointer correctly (using the with context):
distributions {
main {
baseName = 'app'
contents {
into('') {
File.createTempDir().with {
def tmpLog = new File(absolutePath, 'logs')
println tmpLog.absolutePath
tmpLog.mkdirs()
from (absolutePath) {
includeEmptyDirs = true
}
}
// ...
}
// ...
}
}
}
This seems odd answering this so late. But, there are 2 issues here.
We should really avoid creating empty directories. But, if we must...we must.
The previous examples attempt to create empty directories outside of the current project, which seems to go against the goal of most builds. We can avoid this and work more naturally within gradle by adding a custom task.
plugins {
id 'java'
id 'distribution'
}
group 'org.example'
version '1.0-SNAPSHOT'
task createEmptyLogDir() {
ext {
buildDir = layout.buildDirectory.dir('empty_dirs')
}
doFirst {
File.createTempDir().with {
def dir = new File(buildDir.get().asFile, 'logs')
dir.mkdirs()
}
}
outputs.dir(buildDir)
}
tasks.assembleDist.configure {
dependsOn('createEmptyLogDir')
}
distributions {
main {
distributionBaseName = 'app'
contents {
into('lib') {
from tasks.jar
}
from tasks.createEmptyLogDir {
includeEmptyDirs = true
}
}
}
}
This has the advantage of building within the build directory, using task inputs/outputs for up-to-date checks, and cleaning up.
An important note is that we cannot just create the distribution with empty directories, alone. This will be seen as no source and up-to-date. So, I added the jar in this example. Tested with gradle 7.1.
I am trying to extract a resource from a dependent jar file but it seems that I am missing something regarding the time when this code is evaluated.
I would like to do the following:
distributions {
main {
contents {
into("etc") {
from zipTree(configurations.runtime.filter { it.name.startsWith('myLib') }.singleFile)
include "logback.xml"
}
}
}
}
However on this I get:
A problem occurred evaluating script.
Expected configuration ':runtime' to contain exactly one file, however, it contains no files
Can this section be delayed to execution?
I doubt it is related to the execution order. The line
configurations.runtime.filter { it.name.startsWith('myLib') }
is not returning any results. Maybe you should try configurations.runtime.files.filter ....
In my settings.gradle file, I originally had this which works
include 'server', 'webapp'
I modify it to this and it breaks
include 'server', 'webapp'
modifyProjectNames(rootProject, "sdi-")
def modifyProjectNames(project, prefix) {
project.children.each {
it.name = prefix + it.name
}
}
It says "project with path ':webapp' could not be found in root project 'SDI'" so I modify my file to this instead
include 'master', 'sdi-webapp'
modifyProjectNames(rootProject, "sdi-")
def modifyProjectNames(project, prefix) {
project.children.each {
it.name = prefix + it.name
}
}
I get the same exact failure yet I have no :webapp in settings.gradle so it must be my main gradle build file so I rename that project now as well to :sdi-webapp so now the error changes slightly to
Project with path ':sdi-webapp' could not be found in root project 'SDI'.
I have a project structure like so
SDI
- server
- webapp
and I want all the project names to be sdi-server and sdi-webapp, etc. etc. BUT I want the directories to be called
/SDI/server instead of /SDI/sdiserver
/SDI/webapp instead of /SDI/sdiwebapp
ie. no sense in being redundant there.
EDIT
I tried the first answer on another project like so
include 'master', 'toneserver','webserver'
modifyProjectNames(rootProject, "sdi-")
def modifyProjectNames(project, prefix) {
project.children.each {
it.name = prefix + it.name
}
}
rootProject.children.each {proj ->
proj.projectDir = file(proj.name - "sdi-")
}
It simply failed with
Project with path ':toneserver' could not be found in root project 'stserver'
It kind of works if you go through your build.gradle file and modify EVERY reference to also have the prefix which kind of defeats that programming rule of trying not to repeat yourself :( :( so if I ever want to modify the prefix, I have to do an ugly find and replace :(...ick. but it does work.
thanks,
Dean
as a workaround, you ca try to use the projectnames you prefere (SDI-...) in your include calls and change the projectDir of each subproject:
rootproject.children.each{proj->
proj.projectDir = file(proj.name - "SDI-")
}
cheers,
rene
What's the best way to build environment-specific web.xml entries in grails?
I need to make certain modifications for production only, as they break running locally.
Any thoughts?
You can create scripts/_Events.groovy with an event handler for the 'WebXmlEnd' event which is fired once Grails and the plugins have finished making their changes. Update the XML with plain search/replace or via DOM methods by parsing the XML and write out the updated file:
import grails.util.Environment
eventWebXmlEnd = { String filename ->
if (Environment.current != Environment.PRODUCTION) {
return
}
String content = webXmlFile.text
// update the XML
content = ...
webXmlFile.withWriter { file -> file << content }
}
Here's the solution that's i'm using, from the guy over at death-head.ch
first install the templates
grails install-templates
then customize the web.xml you'll find in src/templates/war/web.xml. I chose to make a web_dev.xml and a web_prod.xml and delete the web.xml. I wanted web_prod.xml to contain a security-constraint block. anyway...
Place the following in BuildConfig.groovy:
// #########################################################
// ## Can't use environment switching block because BuildConfig doesn't support it.
// ## #url http://jira.grails.org/browse/GRAILS-4260
// ## So use this workaround:
// ## #url http://death-head.ch/blog/2010/09/finally-solved-the-base-authentication-in-grails/
// #########################################################
switch ("${System.getProperty('grails.env')}") {
case "development":
if (new File("/${basedir}/src/templates/war/web_dev.xml").exists()) {
grails.config.base.webXml = "file:${basedir}/src/templates/war/web_dev.xml"
}
break;
default:
if (new File("/${basedir}/src/templates/war/web_prod.xml").exists()) {
grails.config.base.webXml = "file:${basedir}/src/templates/war/web_prod.xml"
}
break;
}
Good luck!
I've never tried it, but it should be possible to specify the grails.config.base.webXml parameter in BuildConfig.groovy dependant on the current environment.
There's a list of available BuildConfig settings here
EDIT
Actually, due to this issue, this isn't a way forward :-( Maybe passing the property like:
grails -Dgrails.config.base.webXml=/path/to/web.xml
Is all that's possible?