Gradle, OSGI and dependency management - gradle

I'm new to Gradle, please, help me to understand the following. I'm trying to build an OSGI web app via Intellij Idea + Gradle. I've found that Gradle has OSGI plugin, which is described here:
https://docs.gradle.org/current/userguide/osgi_plugin.html
But I have no idea on how to add dependency on, for example, org.apache.felix.dependencymanager which is OSGI bundle. So, I need this jar while compilation, and I don't need it in my resulting jar. I think, that I need something similar to maven 'provided' scope, or something like that.
P.S. Does anyone understand, what 'TBD' means in Gradle documentation? Does this means it has to be implemented in future, or is some mechanism is implemented, but is not yet described in docs?

Please check out the plugin I wrote, osgi-run, which was designed to make it extremely easy to play with OSGi without using any external tools like Eclipse (though osgi-run can generate a Manifest file for you, which you can point at from your IDE to get IDE OSGi support - this is what I do using IntelliJ), just Gradle.
With osgi-run, you just add a dependency to whatever you want as with any Java project... whether it should be provided by the environment or not does not matter at compile time, this is a deployment-time concern.
For example, add to your build.gradle file:
apply plugin: 'osgi' // or other OSGi plugin if you prefer
repositories {
mavenCentral() // add repos to get your dependencies from
}
dependencies {
compile "org.apache.felix:org.apache.felix.dependencymanager:4.3.0"
}
Note: the osgi plugin is just required to turn your jar into a bundle. osgi-run does not do that.
If you have any runtime dependencies that should be present in the OSGi environment but not in the compile classpath, do something like this:
dependencies {
...
osgiRuntime 'org.apache.felix:org.apache.felix.configadmin:1.8.8'
}
Now write some code, and once you're ready to run a OSGi container with your stuff in it, add these lines to the build.gradle file:
// this should be the first line
plugins {
id "com.athaydes.osgi-run" version "1.4.3"
}
...
// deployment to OSGi container config
runOsgi {
// which bundles do you want to add?
// transitive deps will be automatically added
bundles += project
// do not deploy jars matching these regexes (not needed, this is the default)
excludedBundles = ['org\\.osgi\\..*']
// make the manifest visible to the IDE for OSGi support
copyManifestTo file( 'auto-generated/MANIFEST.MF' )
}
Run:
gradle createOsgiRuntime
And find your full OSGi environment, ready to run, in the build/osgi directory.
Run it with:
build/osgi/run.sh # or run.bat in Windows
You can even run it during the build already:
gradle runOsgi

So you probably want to make your own provided configuration.
configurations {
// define new scope
provided
}
sourceSets {
// add the configurations to the compile classpath but not runtime
main.compileClasspath += configurations.provided
// be sure to add the provided configs to your tests too if needed
test.compileClasspath += configurations.provided
}
dependencies {
// declare your provided dependencies
provided 'org.apache.felix:org.apache.felix.dependencymanager:4.3.0'
}
Also the suggestion above about using the bndtool directly instead of the gradle provided osgi plugin is a good one. The gradle plugin has many deficiencies and is really just a wrapper to the bndtool anyways. Also the gradle team has declared they do not have the bandwidth or expertise to fix the osgi plugin [1].
[1] https://discuss.gradle.org/t/the-osgi-plugin-has-several-flaws/2546/5

Related

In a multi-module project can Gradle build a plugin as one module and then use that plugin in the same build?

We have a Gradle project with a bunch of modules. One of those modules is a custom code generator, written as a Gradle plugin. We want to run that code-generator plugin in another module later in the same overall multi-module build, in order to test the code generator.
We know how to create a separate project on the fly and run the code generator in that, but we need to run the code generator in the main project, not in a temporary test project.
Nothing we have tried works, and the Gradle documentation doesn't appear to address this. It seems to be fundamental to Gradle's design, because the entire set of plugins used in a build is basically a single program, assembled at the start. Trying to add a just-now-built plugin after the fact seems unsupported, or we're missing something.
The best we've been able to come up with so far is to implement the plugin in Java (Kotlin would also have worked), so the Gradle plugin is just a thin Gradle skin over the implementation, and call the Java implementation directly when running the code generator in the other module. This works, but it means we aren't actually testing the Gradle portion of the code generator.
This is natively supported in Maven (maven multi-module project with one plugin module, and https://maven.apache.org/guides/mini/guide-multiple-modules.html), which is not surprising because every plugin in Maven runs in a separate class loader. If it's not possible in Gradle, that would be one of the few cases where Gradle doesn't have feature parity.
A hacky way to do this is to run the newly-compiled plugin via Gradle's test kit runner.
A cleaner way to do this is to write plugins as thin shells of code written to Gradle's API that delegate the real work to plain old Java (or Kotlin) utility methods. This has a number of advantages:
You can unit test the utility methods.
You can use the utility methods for other purposes unrelated to the plugin.
You can call the utility methods directly from other modules in the project, thereby accomplishing what the plugin would have done if you could have built it and then called it in the same build.
To expand on the above answer.
Instead of calling the plugin like a plugin, add a main method that accepts the same parameters that Gradle plugin configuration passed to the plugin.
Then call the plugin's main using Gradle's Java exec task:
task(generateFoo, type: JavaExec) {
main = 'com.bar.Foo'
classpath = configurations.runtimeClasspath
args = ["arg1", "${projectDir}/src/generated/java"]
}
Note the args: those are the same pieces of information that used to be passed in via Gradle configuration:
apply plugin: 'foo-plugin'
generateFoo {
theArg "arg1"
outputDir "${projectDir}/src/generated/java"
}
Because the runtime classpath used by Java exec is the one for the calling module, you may encounter runtime classloader problems.
If that happens, it's easily fixed. Just change the rewritten plugin to a fat jar:
task fatJar(type: Jar) {
manifest {
attributes 'Implementation-Title': 'Foo Fat JAR', 'Main-Class': 'com.bar.Foo'
}
baseName = project.name + '-exec'
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
artifacts {
archives fatJar
}
And then execute the fat jar with Java exec:
def fooGenerate = task(generateFoo, type: JavaExec) {
main = 'com.bar.Foo'
classpath = files("${projectDir}/../foo-plugin-module/build/libs/foo-plugin-module-exec.jar")
args = ["arg1", "${projectDir}/src/generated/java"]
}
Finally, make the dependent module's compile task depend on the code generation:
compileJava.mustRunAfter fooGenerate
If you use the fatJar approach, you don't even need to declare implementation project(":foo") in the dependent modules.
It might be also be possible to use Gradle's composite builds for this (https://docs.gradle.org/current/userguide/composite_builds.html).

What is supposed to happen to dependencies after gradle build?

I am trying out Gradle, and am wondering, what is supposed to happen to a project's dependencies after you run gradle build? For example, my sample projects don't run on the command line after they are built, because they are missing dependencies. They seem to compile fine, as gradle doesn't give me errors or warnings about finding the dependencies.
Gradle projects I've made in IntelliJ Idea have the same problem. They compile and run inside the IDE, but are missing dependencies and can't run on the command line.
So what is supposed to happen to the dependencies I declare in the build.gradle file? Shouldn't they be output somewhere together with my .class files? Otherwise, what is the point of gradle when I could manage this by editing my classpath?
Edit: Here is my build.gradle file:
apply plugin: 'java'
jar {
manifest {
attributes('Main-Class': 'Animals')
}
}
repositories {
flatDir{
dirs "D:\\libs\\gradleRepo"
}
}
dependencies {
compile name: "AnimalTypes-1.0-SNAPSHOT"
}
sourceSets{
main{
java {
srcDirs=['src']
}
}
}
Your Gradle build only takes care of the compile time and allows you to use the specified dependencies in your code (it adds them to the compile classpath). But it does not take care of the runtime. Once the JAR is build, you need to specify the runtime classpath and provide all required dependencies.
You may think, that this is bad or a disadvantage, but actually it is totally fine and intended, because if you build a Java library, you won't need to execute it, you just want to specify it as a dependency for another project. If you would distribute your library to a Maven repository, all dependencies from Maven repositories (module dependencies) would end up in a POM descriptor as transitive dependencies.
Now, if you want to build a runnable Java application, simply use the Gradle Application Plugin (apply plugin: 'application'), which will create a ZIP file containing the dependencies and start scripts providing your runtime classpath for execution.
Third-party plugins can also produce so-called fat JARs, which are JAR files with all dependencies included. It depends on your use case if you should use them, because often dependency management via repositories is the better way to go.

gradle how configurations is used

Example in the gradle war plugin: why need to define moreLibs like the following? please explain:
configurations {
moreLibs
}
dependencies {
moreLibs ":otherLib:1.0"
}
war {
classpath configurations.moreLibs
webXml = file('src/someWeb.xml')
}
Can we define anything inside configurations?
configurations {
foobar
}
I have seen these in many places. Can anyone explain?
Yes, you can write anything in the configurations block and it will create a new configuration with that name and you can also further configure it, e. g. by setting its transitive property to false and other stuff.
A custom configuration is just a name for which you can define dependencies that are then resolved transitively by Gradle automatically and can be used for various purposes where you need those resolved files.
In your example you define a moreLibs configuration, add a dependency to it that will be resolved transitively by Gradle and then added to the wars lib directory.
You don't have to do this if you don't have a need to. All libs in the runtime configuration (and thus also those in the compile configuration) are automatically added to the wars lib directory. But if you for some reason need additional libs in there that you don't want to add to compile or runtime, you can do it this way.
Another example of where a custom configuration can be useful is if you want to use a custom Ant task. You define a custom configuration, add the Ant tasks dependency to it, then you let Gradle transitively resolve it and can add the whole fileset as classpath to the taskdef for Ant.

I need debugging tips for a broken project dependency

It's configured the same here as it is everywhere else. In fact I use an import for configuring integration tests. I've done everything I can think of including a rename of local integration test tasks and configurations, including looking at the dependency tree, including running with --debug. Yet for some reason Gradle insists that the property integrationTest doesn't exist on the sourceSet for an inter-project dependency:
integrationTestCompile project(':components:things-components:abc-stuff').sourceSets.integrationTest.output
...now I'm not particularly fond of this syntax and I've already griped up a storm about inter-project test dependencies and how they should be in a test utility component. However, I'm doing it this way because this appears to be what IntelliJ will accept. Writing like this causes trouble:
integrationTestCompile project(path: ':components:things-components:abc-stuff', configuration: 'integrationTest')
How can I figure this out? I just don't get why only one project has this issue.
For the record, I've also tried:
integrationTestCompile project(path: ':components:things-components:abc-stuff', configuration: 'integrationTestCompile')
The issue is that Gradle hasn't been told to make a jar for you that you can consume in your other project.
integrationTestCompile project(':components:things-components:abc-stuff').sourceSets.integrationTest.output
Gets the classFiles and the dependencies, thats why its working using that notation. If you want to use the configuration notation you will need to tell gradle to publish a jar on the integrationTest. The jar doesn't have to be published to a repository, but will be used for the internal builds.
You can do this by doing:
configurations {
integrationTest
}
task integrationTestJar (type: Jar) {
baseName = "${project.name}-integ-test"
from sourceSets.integrationTest.output
}
artifacts {
integrationTest integrationTestJar
}
If you end up doing this in a log of projects, I would recommend writing a quick plugin that does this for you.

How to bundle dependencies in Gradle for multiple applications

I'd like to somehow enable shared dependency management between different applications using Gradle and have created a hierarchy of 'bundles' essentially packaging Java, Spring, Groovy and various test artifacts.
The root of the 'bundling' consists of config as below:
ext {
// Spring-specific
springVersion = '4.1.7.RELEASE'
springCore = "org.springframework:spring-core:${springVersion}"
springContext = "org.springframework:spring-context:${springVersion}"
springBeans = "org.springframework:spring-beans:${springVersion}"
springBase = [springCore, springContext, springBeans]
... and more Spring
// Miscellaneous
sourceCompatibility = 1.8
groovy = 'org.codehaus.groovy:groovy-all:2.4.4'
servlet = 'javax.servlet:javax.servlet-api:3.1.0'
... and more
}
subprojects {
// Enabling Groovy, Wrapper
}
With a 'Spring bundle' configured as follows:
dependencies {
compile(
groovy,
springBase,
)
}
JAR artifacts like my common-spring then depend on the above 'bundle' - and other projects depend on common-spring.
Building locally using Gradle's Maven plugin, this works when I also install the 'bundles' and 'common' to my local Maven repository. However, I am unsure if this is the way I'm 'supposed' to do it in Gradle.
An alternative (and perhaps more correct?) approach could be publishing to Artifactory and resolving artifacts from there, thus (hopefully?) not using any Maven-y stuff at all.
Or is there a completely different and simpler way I could centralize which versions I use of various third-party artifacts?
Yes, this is a good way to share dependencies and in fact Gradle's build uses this same technique.
One difference is that they create a single map to store all of the bundles and wrap them all in a list.
ext.libs = [:]
libs.springCore = ["org.springframework:spring-core:${springVersion}",]
...

Resources