Passing flyway.target and flyway.initVersion property to flywayMigrate gradle task results in cast exception error - gradle

Attempting to pass target property to FlywayMigration gradle task
gradle flywayMigrate -d -Pflyway.target='1.0.0'
or
gradle flywayMigrate -d -Pflyway.target=1.0.0
or
flyway {
url = 'jdbc:h2:file:target/foobar'
user = 'sa'
target = '1'}
fails with org.codehaus.groovy.runtime.typehandling.GroovyCastException
09:08:25.855 [ERROR] [org.gradle.BuildExceptionReporter] Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '1.0.0' with class 'java.lang.String' to class 'com.googlecode.flyway.core.api.MigrationVersion'
Versions and platforms used:
'com.googlecode.flyway:flyway-gradle-plugin:2.2'
gradle -version
Gradle 1.6
Gradle build time: Tuesday 07 May 2013 9:12:14 AM
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.8.4 compiled on May 22 2012
Ivy: 2.2.0
JVM: 1.7.0_05 (Oracle Corporation 23.1-b03)
OS: Mac OS X 10.8.3 x86_64
It appears that this affects when one attempts to set two properties:
target
initVersion
On closer look both methods have overridden setters that accept different types as argument:
Sets the version to tag an existing schema with when executing init.
Parameters:
initVersion The version to tag an existing schema with when executing init. (default: 1)
740
741 public void setInitVersion(MigrationVersion initVersion) {
742 this.initVersion = initVersion;
743 }
Sets the version to tag an existing schema with when executing init.
Parameters:
initVersion The version to tag an existing schema with when executing init. (default: 1)
749
750 public void setInitVersion(String initVersion) {
751 this.initVersion = new MigrationVersion(initVersion);
752 }
However it appears that in the gradle target setting those priorities groovy is not matching correct override type when invoking setter on those two methods:
/**
* Sets this property on this Flyway instance if a value has been defined.
* #param flyway The Flyway instance.
* #param property The property to set.
*/
private void propSet(Flyway flyway, String property) {
def value = prop(property);
if (value != null) {
flyway[property] = value;
}
}
Issue 574 has been opened with Flyway
perhaps they can share some more insights on correct usage for setting above mentioned properties.

Related

How to get values provided by Groovy DSL from Kotlin in Gradle

Suppose following configuration:
build.dependencies.gradle:
ext {
libraries = [:]
}
libraries += [
library : [group: 'com.example', name: 'library', version: '1.1.1']
]
build.gradle.kts:
apply(from = "build.dependencies.gradle")
dependencies {
implementation(libraries["library"]) // does not work
}
Is there a way to get values provided by Groovy script in build.gradle.kts?
It doesn’t work because Kotlin is statically/strongly typed language unlike Groovy. libraries is not defined on any object from Gradle’s API.
You can access it like so:
dependencies {
implementation((project.extra["libraries"] as LinkedHashMap<*, *>)["library"]!!)
}
println(project.extra["libraries"])
project.extra[“libraries”] returns an Object so we need to cast it correctly in order to get the next value. It is also marked as #Nullable so hence the !! operator.
—
A better way to manage dependency versions is to leverage Java Platform plugin.

How do I re-use gradle definitions across projects

I am working on a set of projects that each uses Gradle as the build tool. This is not a multi-project setup although I want to be able to re-use some common Gradle scripts across each project for consistency as the projects are related.
For example, for the Java component, I want the manifest file in the generated JAR file to have the same information. In particular, all the projects will have the same major and minor versions numbers, while the patch version will be project specific.
Here's what I've tried so far:
master.gradle - to be shared across projects
group 'com.example'
ext.majorVersion = 2
ext.minorVersion = 3
ext.patchVersion = 0; // Projects to override
def patchVersion() {
return patchVersion;
}
apply plugin: 'java'
jar {
manifest {
attributes 'Bundle-Vendor': 'Example Company',
'Bundle-Description': 'Project ABC',
'Implementation-Title': project.name,
'Implementation-Version': majorVersion + '.' + minorVersion + '.' + patchVersion()
}
}
build.gradle - for one of the projects
apply from: 'master.gradle'
patchVersion = 3
task hello {
println 'Version: ' + majorVersion + '.' + minorVersion + '.' + patchVersion
}
If I run gradle hello jar from the command line, I get Version: 2.3.3 from the hello task. However, the JAR file manifest contains 2.3.0 which is not what I want. How do I get the correct patch version into the manifest? And more generally, how do I let projects supply information to the master scripts?
Based on #Oliver Charlesworth's suggestion I came up with the following. I had to write a simple plugin to hold the version information and use it as an extension object. Please note (as suggested by the comments in the gradle files), the order in which items are applied and set is very important. Different orderings result in compiler errors or values used before they are set.
If anyone wants to suggest improvements, please do so.
master.gradle
group 'com.example'
// N.B. The individual project must have applied the semantic version
// plugin and set the patch version before applying this file.
// Otherwise the following will fail.
// Specify the major and minor version numbers.
project.semver.major = 2
project.semver.minor = 3
project.version = project.semver
apply plugin: 'java'
jar {
manifest {
attributes 'Bundle-Vendor': 'Example Company',
'Bundle-Description': project.description,
'Implementation-Title': project.name,
'Implementation-Version': project.semver
}
}
build.gradle
// Describe the project before importing the master gradle file
project.description = 'Content Upload Assistant'
// Specify the patch version
apply plugin: SemanticVersionPlugin
project.semver.patch = 3
// Load the master gradle file in the context of the project and the semantic version
apply from: 'master.gradle'
The simple plugin can be found below. At the moment it is with the application source code, but it should be moved out into a library, along with the master gradle file.
buildSrc/src/main/groovy/SemanticVersionPlugin.groovy
import org.gradle.api.Plugin
import org.gradle.api.Project
class SemanticVersionPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create('semver', SemanticVersion)
}
}
class SemanticVersion {
int major
int minor
int patch
String toString() {
return major + '.' + minor + '.' + patch
}
}

Maven plugin testing with Groovy: access to generated class?

I'm developping a Maven plugin and some of my tests are done through maven-invoker-plugin based on Groovy scripts (https://maven.apache.org/plugin-developers/plugin-testing.html).
This Maven plugin generates a Java class and I'd like to test this generated class behaviour with Groovy scripts.
I'm facing a problem with these Groovy scripts saying that it could not resolve the class:
Running post-build script: <blah>\verify.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Script1.groovy: 21: unable to resolve class com.mycompany.MyClass
# line 21, column 1.
import com.mycompany.MyClass
Here is the generated Java class from my plugin:
package com.mycompany;
public final class MyClass {
public static String getSomething() {
return "something";
}
}
Here is an extract of my Groovy test verify.groovy:
File generatedJavaFile = new File( basedir, "target/my-plugin/com/mycompany/MyClass.java" );
assert generatedJavaFile.exists()
assert generatedJavaFile.isFile()
File generatedClassFile = new File( basedir, "target/classes/com/mycompany/MyClass.class" );
assert generatedClassFile.exists()
assert generatedClassFile.isFile()
import com.mycompany.MyClass
assert MyClass.getSomething() == "the expected result"
I was wondering if something needs to be specified to the maven-invoker-plugin configuration to include the tested Maven project, or if it's just not possible and I need to find another way...
Thanks,
Benoît.

Gradle plugin for XML Beans

I am trying to write a Gradle plugin for XML Beans. I have started with one of the 'Hello from Gradle' plugin examples, and also a plugin published by R. Artavia here. That plugin went straight to jar - I am trying to only generate source. The generated source must then be compiled with other project source and included in a single jar. Other goals include
- full plugin - all I should need is "apply plugin: 'xmlbean'"
- I can configure source/code gen location and some features if I want to
- It detects whether it needs to be rebuilt. (well, eventually!!!)
I am off to a pretty good start, but am blocked defining a new sourceSet. I am getting an error "No such property 'srcDirs'" (or 'srcDir'). It seems there is something I have to define someplace to make a new sourceSet work but I cannot find it. I have tried several different syntaxes (with/without equal sign, brackets, srcDir/srcDirs, etc. - nothing is working...
What do I need to do inside a plugin to make a new sourceSet entry be properly recognized?
Thank you!
JKE
File: xmlbean.gradle (includes greeting plugin for the moment for debugging)
apply plugin: xmlbean
apply plugin: 'java'
xmlbean {
message = 'Hi'
greeter = 'Gradle'
}
class xmlbean implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("xmlbean", xmlbeanExtension)
Task xmlbeanTask = project.task('xmlbean')
xmlbeanTask << {
project.configurations {
xmlbeans
}
project.dependencies {
xmlbeans 'org.apache.xmlbeans:xmlbeans:2.5.0'
}
project.sourceSets {
main {
java {
srcDirs += '$project.buildDir/generated-source/xmlbeans'
}
}
xmlbeans {
srcDirs = ['src/main/xsd']
}
}
ant.taskdef(name: 'xmlbean',
classname: 'org.apache.xmlbeans.impl.tool.XMLBean',
classpath: project.configurations.xmlbeans.asPath)
ant.xmlbean(schema: project.sourceSets.xmlbean.srcDir,
srconly: true,
srcgendir: "$project.buildDir/generated-sources/xmlbeans",
classpath: project.configurations.xmlbeans.asPath)
println "${project.xmlbean.message} from ${project.xmlbean.greeter}"
}
project.compileJava.dependsOn(xmlbeanTask)
}
}
class xmlbeanExtension {
String message
String greeter
}
File: build.gradle
apply from: '../gradle/xmlbeans.gradle'
dependencies {
compile "xalan:xalan:$ver_xalan",
":viz-common:0.0.1",
":uform-repository:0.1.0"
}
Console: Error message:
:idk:xmlbean FAILED
FAILURE: Build failed with an exception.
* Where:
Script 'C:\jdev\cpc-maven\try.g2\comotion\gradle\xmlbeans.gradle' line: 32
* What went wrong:
Execution failed for task ':idk:xmlbean'.
> No such property: srcDirs for class: org.gradle.api.internal.tasks.DefaultSourceSet_Decorated
...
BUILD FAILED
Gradle info: version 2.5 / groovy 2.3.10 / JVM 7u55 on Windows 7 AMD64
You should try to become familiar with the Gradle DSL reference guide, because it's a huge help in situations like this. For example, if you click on the sourceSets { } link in the left navigation bar, you're taken to this section on source sets.
From there, you'll discover that the sourceSets {} block is backed by a class, SourceSetContainer. The next level of configuration nested inside is backed by a SourceSet object, and then within that you have one or more SourceDirectorySet configurations. When you follow the link to SourceDirectorySet, you'll see that there are getSrcDirs() and setSrcDirs() methods.
So how does this help? If you look closely at the exception, you'll see that Gradle is saying it can't find a srcDirs property on DefaultSourceSet_Decorated, which you can hopefully infer is an instance of SourceSet. That interface does not have an srcDirs property. That's because your xmlbeans {} block is configuring a SourceSet, not a SourceDirectorySet. You need to add another nested configuration to gain access to srcDirs.
At this point, I'm wondering whether a new source set is the appropriate solution. Unfortunately it's not clear to me exactly what the plugin should be doing, so I can't offer any alternatives at this point.

How to update the <latest> tag in maven-metadata-local when installing an artifact?

In the project I am working on we have various multi-module projects being developed in parallel, some of which are dependent on others. Because of this we are using using version ranges, e.g. [0.0.1,), for our internal dependencies during development so that we can always work against the latest snapshot versions. (I understand that this isn't considered best practice, but for now at least we are stuck with the current project structure.) We have build profiles set up so that when we perform a release all the version ranges get replaced with RELEASE to compile against the latest released version.
We have to use ranges as opposed to LATEST because when installing an artifact locally, the <latest> tag inside maven-metadata-local.xml is never updated, and so specifying LATEST will get the last version deployed to our Artifactory server. The problem with the ranges though is that the build process seems to have to download all the metadata files for all the versions of an artifact to be able to determine the latest version. As our project goes on we are accumulating more and more versions and artifacts so our builds are taking longer and longer. Specifying LATEST avoids this but means that changes from local artifact installs are generally not picked up.
Is there any way to get the <latest> tag in the maven-metadata-local.xml file to be updated when installing an artifact locally?
If you are working with SNAPSHOT's you don't need version ranges apart from that never use version ranges (only in extrem rare situtions). With version ranges your build is not reproducible which should be avoided in my opinion under any circumstance.
But you can use things like this:
<version>[1.2.3,)</version
but as you already realized that caused some problems, but I would suggest to use the versions-maven-plugin as an alternative to update the projects pom files accordingly.
mvn clean versions:use-latest-versions scm:checkin deploy -Dmessage="update versions" -DperformRelease=true
This can be handled by CI solution like Jenkins. But I got the impression that you are doing some basic things wrong. In particular if you need to use version ranges.
I had the same problem, so I wrote a maven plugin to handle it for me. It's a pretty extreme workaround, but it does work.
The documentation for creating maven plugins is on The Apache Maven Project. You could just create a plugin project from the command line archetype and add this mojo to your project.
/**
* Inserts a "latest" block into the maven-metadata-local.xml in the user's local
* repository using the currently configured version number.
*
* #version Sep 23, 2013
*/
#Mojo( name = "latest-version", defaultPhase = LifecyclePhase.INSTALL )
public class InstallLatestVersionMojo extends AbstractMojo {
/**
* Location of the .m2 directory
*/
#Parameter( defaultValue = "/${user.home}/.m2/repository", property = "outputDir", required = true )
private File repositoryLocation;
#Parameter( defaultValue = "${project.groupId}", property = "groupId", required = true )
private String groupId;
#Parameter( defaultValue = "${project.artifactId}", property = "artifactId", required = true )
private String artifactId;
/**
* Version to use as the installed version
*/
#Parameter( defaultValue = "${project.version}", property = "version", required = true )
private String version;
public void execute() throws MojoExecutionException, MojoFailureException {
try {
// Fetch the xml file to edit from the user's repository for the project
File installDirectory = getInstallDirectory(repositoryLocation, groupId, artifactId);
File xmlFile = new File(installDirectory, "maven-metadata-local.xml");
Document xml = getXmlDoc(xmlFile);
if (xml != null) {
// Fetch the <latest> node
Node nodeLatest = getNode(xml, "/metadata/versioning/latest");
if (nodeLatest == null) {
// If <latest> does not yet exist, insert it into the <versioning> block before <versions>
nodeLatest = xml.createElement("latest");
Node versioningNode = getNode(xml, "/metadata/versioning");
if (versioningNode != null) {
versioningNode.insertBefore(nodeLatest, getNode(xml, "metadata/versioning/versions"));
}
}
// set the version on the <latest> node to the newly installed version
nodeLatest.setTextContent(version);
// save the xml
save(xmlFile, xml);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void save(File xmlFile, Document xml) throws TransformerFactoryConfigurationError, TransformerException {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
Result output = new StreamResult(xmlFile);
Source input = new DOMSource(xml);
transformer.transform(input, output);
}
private Node getNode(Document source, String path) throws XPathExpressionException{
Node ret = null;
XPathExpression xPath = getPath(path);
NodeList nodes = (NodeList) xPath.evaluate(source, XPathConstants.NODESET);
if(nodes.getLength() > 0 ) {
ret = nodes.item(0);
}
return ret;
}
private XPathExpression getPath(String path) throws XPathExpressionException{
XPath xpath = XPathFactory.newInstance().newXPath();
return xpath.compile(path);
}
private File getInstallDirectory(File repositoryLocation, String groupId, String artifactId) {
String group = groupId.replace('.', '/');
return new File(repositoryLocation, group + "/" + artifactId);
}
private Document getXmlDoc(File xmlFile) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
return dBuilder.parse(xmlFile);
}
}
How about defining those internal dependencies as modules in one reactor pom? That way you'll compile against the compiled sources (in target/classes) instead of against a jar, and you'll always have the latest code.

Resources