I want to store an input file in a JAR created with a subclass of the Jar task in Gradle. The input file shall be stored under a different name.
Here is my build.gradle (complete working example; just create dir1/file1.txt first):
task myjar(type: MyJarTask);
class MyJarTask extends Jar {
#InputFile
File file1 = new File('dir1/file1.txt');
public MyJarTask() {
configure {
baseName 'foo'
from {
file1;
// comment out the next line to eliminate the error:
rename { String fileName -> fileName.replace('file1.txt', 'file2.txt'); }
}
}
}
}
Running this via gradle myjar gives the following error:
* What went wrong:
Circular dependency between the following tasks:
:myjar
\--- :myjar (*)
(*) - details omitted (listed previously)
When I comment out the line with the rename, everything works! (Of course the file is not renamed.)
What is the reason for this surprising behavior? Are we witnessing a Gradle bug?
Please do not suggest alternative solutions; I solved the original problem by avoiding the need for the rename. But I would like to learn; I feel I am missing something important.
I got an answer on the Gradle forum by Sterling Greene of Gradleware. Basically, the cause of the circular dependency is this (in my own words):
Closures always return the last value, so the from {} closure returns the value of rename. Incidentally, rename returns a reference to the task itself (why it would do that, I don't know, but that's what its docs say). So the task itself is added to its list of things to copy, and we have a circular dependency.
The solution would be to modify the closure slightly:
from(file1) {
//file1;
rename { String fileName -> fileName.replace('file1.txt', 'file2.txt'); }
}
This runs with no problems, because it uses an overloaded variant of the from() method which always adds the given file to the list of things to copy, not the result of the closure. All in all, that's not exactly intuitive, but there it is.
Well i also got same error I thought error was due to renaming of files but in my case error was due to dual dependency on module(:backend)and java library dependency on android module(:app) I got it sorted by following steps:
Press Ctrl+Shift+Alt+S or Go to File->Project Structure this will open project Structure Dialog box.
On left side PANEL ,under modules section choose java library (e.g jjLibrary) there was two compile dependency :app and :backend just have to remove both of these dependencies.As :backend already have compile dependency on java library and :app is android module which cannot have dependency on java library.Press Ok.
Save and Sync gradle files ..
And the error has gone..Hope this helps!!!Cheers
Related
I'm fairly new to Gradle, and am enamored by its task-driven approach and customizability. So I have a question that hopefully helps me understand how to do something beyond the basics.
Suppose I have a simple text file that contains information about a project's dependencies. For example, something like
- dependency1
from: 'foobar'
version: '1'
- dependency2
from: 'foobaz'
version: '1'
Note that these are not I have some code that would, from this file, generate a file along the lines as follows:
compile 'commons-lang:commons-lang 2.6'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
Is there a way to configure my project so that my project's dependencies are taken from this file (not exclusively)?
Do note that the text file is also used to generate other artifacts that are used by other tasks (for example, a file to be added to a Docker container), so while it may be possible to, say, declare the dependencies normally and generate the text file instead, it is not a trivial process to do so at this time.
Yes, you can write your own groovy function that parses the file and creates a List. Then you can pass this map to the dependencies closures:
List<String> compileLibraries() {
// ... parse yaml and return list
}
dependencies {
compile compileLibraries()
}
Also, to use a yaml-parsing library, consider setting up a custom gradle subproject in the special buildSrc subfolder and writing your helper function there.
See this old post for more details: https://discuss.gradle.org/t/programmatically-adding-dependencies/7575/12
And here for buildSrc projects:
https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:build_sources
In Gradle User Guide Chapter 23. Dependency Management , an example show the difference between Configuration.copy and Configuration.files method :
build.gradle
task copyVsFiles << {
configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }
.each { file -> println file.name }
println()
configurations.sealife.files { dep -> dep.name == 'orca' }
.each { file -> println file.name }
}
Output of gradle -q copyVsFiles
> gradle -q copyVsFiles
orca-1.0.jar
seal-1.0.jar
orca-1.0.jar
seal-2.0.jar
the below explanation confused me. I still don't know the difference. Can anyone help me with this ?
The answer to your question is in the paragraph just after example 23.24 in the Gradle API documentation Chapter 23 Section 24.
Note how the configurations.files method returns version 2 of the seal jar.
In the example above, orca has a dependency on seal-1.0 whereas shark
has a dependency onseal-2.0. The original configuration has therefore
a version conflict which is resolved to the newer seal-2.0 version.
The files method therefore returns seal-2.0 as a transitive dependency
of orca. The copied configuration only has orca as a dependency and
therefore there is no version conflict and seal-1.0 is returned as a
transitive dependency.
To really understand why this happens spend some time looking at the Configuration method in the Gradle docs.
Files does the following:
Resolves this configuration. This locates and downloads the files
which make up this configuration. But only the resulting set of files
belonging to the subset of dependencies specified by the
dependencySpec is returned.
Copy does the following:
Creates a copy of this configuration that only contains the
dependencies directly in this configuration (without contributions
from superconfigurations). The new configuration will be in the
UNRESOLVED state, but will retain all other attributes of this
configuration except superconfigurations. Configuration.getHierarchy()
for the copy will not include any superconfigurations.
How does this apply to the documentation example? Configuration.files resolves the configuration which, as shown by the output in the example, handles the version conflict introduced by shark which depends on seal-2.0.jar. Configuration.copy creates a copy of the configuration and is not yet resolved meaning it does not yet have a version conflict.
There's a bug in a dependency I'm trying to use that makes the build fail. I'm trying to configure a work-around until the dependency's fix is released. The work-around is to remove a class from a jar inside an aar.
The task I need to do this action in is dynamically added, so to complicate things I'm modifying the task in another task.
At this point in time, I'm just trying to find the location of the aar on disk. I'm open to any and all suggestions on how to do this better/easier, as I'm terrible at Gradle. But my explicit question is how can I modify a dependency file after resolution?
// A hack for duplicate BuildConfig in multidex bug.
// remove the duplicate BuildConfig from the multidex test aar
// can't configure before packageAllArmDebugTestClassesForMultiDex task is known, so configure it
// after preBuild.
preBuild.doLast {
packageAllArmDebugTestClassesForMultiDex.doFirst {
project.configurations.getByName("androidTestCompile")
.getDependencies().matching{d -> d.name == "multidex-instrumentation"}
.each{f -> println("Nothing?! " + f.getArtifacts())}
}
}
The packageAllArmDebugTestClassesForMultiDex task is successfully modified to print out the dependency artifacts, but it merely prints Nothing?! [].
I am trying to build a gradle plugin, which does the following:
As part of one its tasks, it creates a new configuration
It adds a DefaultExternalModuleDependency to this configuration - more specifically, it constructs a dependency to the application server zip file (available on Nexus). This information can be overridden by the invoking project as well.
Tries to resolve this newly added dependency and then unpacks the file to a local folder
All of this was working well when I had the details hard coded in a build file, but it looks like adding dependencies as part of a task are not treated the same way as having that information available at the parsing time.
So my question is, how do I get the project to reload the configurations / dependencies?
The code looks like the following:
#TaskAction
void installAppserver() {
Dependency dependency = new DefaultExternalModuleDependency(group,name,version)
Configuration configuration = project.configurations.detachedConfiguration(dependency)
configuration.setTransitive(false)
configuration.files.each { file ->
if (file.isFile() && file.name.endsWith('.zip')) {
println 'Attempting to unzip: ' + file + ' into folder: ' + appServerFolder
new Copy().from(project.zipTree(file)).into(appServerFolder).execute()
}
}
}
The problem is that the actual artifacts are not getting resolved!
A task can't configure the build model (that's what plugins do). It's fine to create and resolve a detached configuration in a task. If this doesn't work, there is likely a problem with the task's code, or the dependency it tries to resolve. Note that dependencies can only be resolved if the correct repository(s) are defined.
Instead of new DetaultExternalModuleDependency() (which is an internal class), project.dependencies.create() should be used. Instead of new Copy().execute() (Task#execute must not be called from user code), project.copy should be used.
I'm using Gradle to build a jar containing an xml file in META-INF. This file has a row like
<property name="databasePlatform" value="${sqlDialect}" />
to allow for different SQL databases for different environments. I want to tell gradle to expand ${sqlDialect} from the project properties.
I tried this:
jar {
expand project.properties
}
but it fails with a GroovyRuntimeException that seems to me like the Jar task attempts to expand properties in .class files as well. So then I tried
jar {
from(sourceSets.main.resources) {
expand project.properties
}
}
which does not throw the above exception, but instead results in all resources being copied twice - once with property expansion and once without. I managed to work around this with
jar {
eachFile {
if(it.relativePath.segments[0] in ['META-INF']) {
expand project.properties
}
}
}
which does what I want, since in my use case I only need to expand properties of files in the META-INF directory. But this feels like a pretty ugly hack, is there a better way to do this?
I stumbled across this post in a thread about a different but closely related issue. Turns out you want to configure the processResources task, not the jar task:
processResources {
expand project.properties
}
For some reason, though, I did have to clean once before Gradle noticed the change.
In addition to #emil-lundberg 's excellent solution, I'd limit the resource processing to just the desired target file:
build.gradle
processResources {
filesMatching("**/applicationContext.xml") {
expand(project: project)
}
}
An additional note: if the ${...} parentheses are causing "Could not resolve placeholder" errors, you can alternatively use <%=...%>. N.B. tested with a *.properties file, not sure how this would work for an XML file.
I've had similar problems migrating from maven to gradle build. And so far the simplest/easiest solution was to simply do the filtering yourself such as:
processResources {
def buildProps = new Properties()
buildProps.load(file('build.properties').newReader())
filter { String line ->
line.findAll(/\$\{([a-z,A-Z,0-9,\.]+)\}/).each {
def key = it.replace("\${", "").replace("}", "")
if (buildProps[key] != null)
{
line = line.replace(it, buildProps[key])
}
}
line
}
}
This will load all the properties from the specified properties file and filter all the "${some.property.here}" type placeholders. Fully supports dot-separated properties in the *.properties file.
As an added bonus, it doesn't clash with $someVar type placeholders like expand() does. Also, if the placeholder could not be matched with a property, it's left untouched, thus reducing the possibility of property clashes from different sources.
here is what worked for me (Gradle 4.0.1) in a multi-module project:
in /webshared/build.gradle:
import org.apache.tools.ant.filters.*
afterEvaluate {
configure(allProcessResourcesTasks()) {
filter(ReplaceTokens,
tokens: [myAppVersion: MY_APP_VERSION])
}
}
def allProcessResourcesTasks() {
sourceSets*.processResourcesTaskName.collect {
tasks[it]
}
}
and my MY_APP_VERSION variable is defined in top-level build.gradle file:
ext {
// application release version.
// it is used in the ZIP file name and is shown in "About" dialog.
MY_APP_VERSION = "1.0.0-SNAPSHOT"
}
and my resource file is in /webshared/src/main/resources/version.properties :
# Do NOT set application version here, set it in "build.gradle" file
# This file is transformed/populated during the Gradle build.
version=#myAppVersion#
I took your first attempt and created a test project. I put a pom file from a jenkins plugin in ./src/main/resources/META-INF/. I assume it is a good enough xml example. I replaced the artifactId line to look like the following:
<artifactId>${artifactId}</artifactId>
My build.gradle:
apply plugin: 'java'
jar {
expand project.properties
}
When I ran gradle jar for the first time it exploded because I forgot to define a value for the property. My second attempt succeeded with the following commandline:
gradle jar -PartifactId=WhoCares
For testing purposes I just defined the property using -P. I'm not sure how you are trying to define your property, but perhaps that is the missing piece. Without seeing the stacktrace of your exception it's hard to know for sure, but the above example worked perfectly for me and seems to solve your problem.