I'm migrating an older Android library from jcenter to mavenCentral in light of their deprecation. All of the actual library modules are written in Java, but several of them have tests written in Kotlin.
Until upgrading (to AS 4.1.3, Gradle 6.8.3), the generated POM(s) never included a compilation dependency on the kotlin-stdlib. Now it appears that applying the kotlin-android plugin causes the generated POM to add it, even though the dependency is included as testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.31".
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.4.31</version>
<scope>compile</scope>
</dependency>
</dependencies>
The question: Was the previously-generated POM wrong in that it should have included kotlin as a compilation dependency, even though it's only used for testing?
Or, do I have to do something special to exclude that "dependency" in this specific case? If so, how could I exclude it if generating the POM when defining the publication like:
afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
....
pom {
name = ...
licenses { }
developers { }
scm { }
}
}
}
}
}
Or, is that not really a good option and I should just rewrite the tests in Java?
It's worth noting that removing the kotlin-android plugin also removes it as a compilation dependency from the POM(s). It seems to have no effect on generating the AAR; it just prevents the tests from running.
Since Kotlin 1.4, the various Kotlin Gradle plugins automatically add a dependency on kotlin-std. See Dependency on the standard library.
You can disable it by adding to gradle.properties:
kotlin.stdlib.default.dependency=false
Going to "answer" my own questions and hope someone corrects me if anything is wrong.
Was the previously-generated POM wrong in that it should have included kotlin as a compilation dependency, even though it's only used for testing?
No, the kotlin-stdlib should not be included in the POM of a release publication for a java/android library that has no kotlin code or kotlin-based dependencies.
I'm making that assertion as I have compiled a java-only app against a version of this library where the kotlin-stdlib compilation condition was removed from the POM, and everything behaved as expected (in that it worked).
If so, how could I exclude it if generating the POM when defining the publication
After defining your POM, you can use pom.withXml from the MavenPom plugin to remove the dependency nodes you want to exclude from the generated POM. e.g.
pom {
licenses { ... }
developers { ... }
scm { ... }
}
pom.withXml {
asNode()
.dependencies
.dependency
.findAll { it.artifactId.text() == "kotlin-stdlib" }
.collect { it.parent().remove(it) }
}
Edit: This does not remove the dependencies from the generated module.json, so this now seems even more wrong than it felt before.
Instead, you can check the Gradle Start Parameters to determine which variant is being targeted by the current Task Requests, and only apply the plugin if the variant should actually include it:
def kolinEnabledVariant = getGradle()
.getStartParameter()
.getTaskRequests()
.toString()
.contains("Debug")
if (kolinEnabledVariant) {
apply plugin: 'kotlin-android'
}
Commenters on a related answer note that the variant name should start with a capital letter.
Or, is that not really a good option and I should just rewrite the tests in Java?
That's giving up.
Related
I'm using gradle plugin as
plugins {
id 'net.ltgt.errorprone' version '2.0.2' apply false
}
Now, I forked net.ltgt.errorprone on Github, did some changes in branch changes. How can I tell Gradle to use the fork instead of upstream?
I've found this 'Fork' git repository as dependency in gradle for dependencies, looking for something similar for plugins.
I solved it using jitpack.io to distribute the forked code into my app as a dep.
You can go to https://jitpack.io/ and enter your git url, it will give you available refs to use.
Also, don't forget to check ref's log so you know jitpack can actually build your dep.
The log is available via icon in Log column.
If the icon is not visible, you can access it via url.
E.g. for commit hash 384433165eba475b113189d05786d4daf17c465c it is located on https://jitpack.io/com/github/johnrengelman/shadow/384433165eba475b113189d05786d4daf17c465c/build.log
There you can see the build is actually broken as the plugins needs at least jdk 11 but jitpack runs it with v1.8.
You can force jitpack tu use another jdk version by creating jitpack.yml in project's root with following content:
jdk: openjdk11
See https://jitpack.io/docs/BUILDING/#java-version for more info.
I've found few ways how to use the fork but I liked this one the most:
Let's say you use these
plugins {
// in my opinion, removing `version` makes it more obvious that we're using JitPack, but it can stay too
id 'com.eriwen.gradle.css' version '2.14.0'
id 'com.eriwen.gradle.js' version '2.14.1'
}
So you can replace them with your fork via adding this (jitpack repository and replacement resolution) to your settings.gradle:
pluginManagement {
repositories {
gradlePluginPortal()
maven {
url 'https://jitpack.io'
}
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'com.eriwen.gradle.css') {
useModule('com.github.eriwen:gradle-css-plugin:9fe88d7') // can also be a branch, i.e. `master-SNAPSHOT`
}
if (requested.id.id == 'com.eriwen.gradle.js') {
useModule('com.github.eriwen:gradle-js-plugin:d15f4ae') // can also be a branch, i.e. `master-SNAPSHOT`
}
}
}
}
I have a Java 17 project using Gradle with a multitude of JavaCPP libraries. I started with a simple demo project that I cloned from the JavaCPP Github repo. This sample project incorporates several native libs such as OpenCV, ffmpeg, etc., so I thought it'd be a good test. And, no surprise, it worked just fine. It brought up my camera, did the face detection, etc. All very cool.
My aim - I want to modularize my JavaCPP projects to be JPMS compliant.
Not easy. So, to troubleshoot, I figure that I would start with good test code, which is why I'm working with the official JavaCPP Gradle demo program.
So, I did the following to convert it to be JPMS compliant:
Created a module-info.java placed down src/main/java. I added the appropriate requires statements (see below).
Modified build.gradle to add several *-platform dependencies and a few other plugins, including JavaFX.
The TL;DR - I got it to work (though the camera appears at an angle in the app window, which is just weird, but I'm assuming I'm still missing a library in module-info.java). The problem is that it only worked after I not only specified numerous additional *-platform dependencies in build.gradle, but also needed to list the actual native platform libraries in module-info.java. So, for instance, I need to add the following statement:
requires org.bytedeco.opencv.macosx.x86_64;
If I do not do that, then I get the following error:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no jniopencv_core in java.library.path: /Users/brk009/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
My main question - How can I make a modular JavaCPP project build and execute properly without hard coding the platform dependent native libraries in module-info.java? I thought that just specifying the *-platform libraries in module-info.java would do it, but nope.
If this was just a project for my own system, then fine - I'd live with it. However, I want to pass some of my example code off to my students. It'd be fine if they all ran Macs. However, my students have quite a heterogeneous platform base (i.e. a mix of Mac, Windows, and Linux users.) Ideally, it'd be great to have a platform-independent codebase and let my program build and run regardless of the platform. Heck, I'd even be happy if I only needed to specify the platform as a parameter for gradlew as a command-line argument, such as indicated here, where I could just specify -PjavacppPlatform=linux-x86_64. But that did not work either.
I did verify that Loader.Detector.getPlatform() returns the correct platform string, and Loader.getCacheDir() returns ~/.javacpp/cache as you would expect.
Any help/guidance would be immensely appreciated! Thank you kindly.
module-info.java
module HelloJavaCPP {
requires java.base;
requires java.desktop;
requires org.bytedeco.javacpp;
requires org.bytedeco.javacpp.macosx.x86_64; // I do NOT WANT to hard code any platform!
requires org.bytedeco.javacv;
requires org.bytedeco.opencv;
requires org.bytedeco.opencv.macosx.x86_64;
requires org.bytedeco.ffmpeg;
requires org.bytedeco.ffmpeg.macosx.x86_64;
requires org.bytedeco.openblas;
requires org.bytedeco.openblas.macosx.x86_64;
}
build.gradle
plugins {
id 'application'
id 'java'
id 'java-library'
id 'org.openjfx.javafxplugin' version '0.0.12'
id 'org.javamodularity.moduleplugin' version '1.8.10'
id 'org.bytedeco.gradle-javacpp-platform' version '1.5.7'
}
group = 'org.hello'
version = '1.5.7'
repositories {
mavenLocal()
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
javafx {
version = "17.0.2"
modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}
dependencies {
api "org.bytedeco:javacv-platform:1.5.7"
api 'org.bytedeco:opencv-platform:4.5.5-1.5.7'
// api "org.bytedeco:opencv-platform-gpu:4.5.5-$version"
api "org.bytedeco:ffmpeg-platform-gpl:5.0-$version"
api 'org.bytedeco:openblas-platform:0.3.19-1.5.7'
testImplementation 'junit:junit:4.13.2'
}
application {
mainModule = "$moduleName"
mainClass = "org.hello.Demo"
}
settings.gradle
I'm including this just incase.
pluginManagement {
repositories {
mavenLocal()
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
gradlePluginPortal()
}
}
rootProject.name = 'HelloJavaCPP'
gradle.rootProject { ext.javacppVersion = '1.5.7' }
Those links posted by Samuel above were immensely helpful. It turns out there are some modularity peculiarities with JavaFX that can wreck havoc when using JavaFX with non-JavaFX modules such as those in JavaCPP. See here.
The key part that was important:
We can either use the run goal of the JavaFX Maven plugin, the java goal of the Exec Maven plugin, or manually launch java with a module path computed from Maven dependencies and option --add-modules ALL-MODULE-PATH.
Once I figured out how to add a JVM argument in Gradle, I was able to remove all hard-coded architecture requires statements, and I can now use gradlew run, let JavaCPP's Loader class do all the work of discovering the architecture itself and loading the appropriate native libraries!
The two most important files that need to change:
module-info.java
Notice how much simpler this becomes, and it has NO hard-coded platform system architecture information, which is exactly what we want:
module HelloJavaCPP {
requires java.base;
requires java.desktop;
requires org.bytedeco.javacpp;
requires org.bytedeco.javacv;
requires org.bytedeco.opencv;
requires org.bytedeco.ffmpeg;
requires org.bytedeco.openblas;
}
build.gradle
The most important change I needed to make (which I got from information posted here was to add a run configuration, and specify the JVM argument `--add-modules
plugins {
id 'application'
id 'java'
id 'java-library'
id 'org.openjfx.javafxplugin' version '0.0.12'
id 'org.javamodularity.moduleplugin' version '1.8.10'
id 'org.bytedeco.gradle-javacpp-platform' version "$javacppVersion"
}
group = 'org.hello'
version = '1.5.7'
repositories {
mavenCentral()
}
ext {
// javacppPlatform - should be autodetected, but can also specify on cmd line
// as -PjavacppPlatform=macosx-x86_64
// javacppPlatform = 'linux-x86_64,macosx-x86_64,windows-x86_64,etc' // defaults to Loader.getPlatform()
javacppPlatform = 'macosx-x86_64' // defaults to Loader.getPlatform()
}
javafx {
version = "17.0.2"
modules = [ 'javafx.graphics','javafx.controls', 'javafx.fxml' ]
}
dependencies {
api "org.bytedeco:javacpp-platform:$javacppVersion"
api "org.bytedeco:javacv-platform:$javacppVersion"
api "org.bytedeco:opencv-platform:4.5.5-1.5.7"
api "org.bytedeco:ffmpeg-platform-gpl:5.0-1.5.7"
api "org.bytedeco:openblas-platform:0.3.19-1.5.7"
testImplementation 'junit:junit:4.13.2'
}
application {
mainModule = "$moduleName"
mainClass = "org.hello.Demo"
}
// THIS WAS THE PRIMARY CHANGE:
run {
jvmArgs = ['--add-modules', 'ALL-MODULE-PATH']
}
It's worth noting that I was able to remove the ext configuration in build.gradle, which allows Loader.getPlatform() to do the work of determining the platform at runtime, and it worked just fine! (I left it in place just for reference purposes.)
I hope this helps others. I did NOT test out building an image, as judging from what I read, that is quite an additional level of complexity. We'll tackle that another time.
Thank you again.
I was reading the documentation for dependency locking in gradle. I put the following incantation in my deps.gradle file and versions are still sliding around.
dependencyLocking {
lockAllConfigurations()
}
I then noticed the caveat "The above will lock all project configurations, but not the buildscript ones" but I could not find docs that explained the difference.
What is the difference and how can I lock all dependencies?
Note that I'm aware of one other way of locking depenencies that does seem to work, but it seems very verbose so I'd like to avoid it if possible:
implementation('com.github.jnr:jffi') {
version {
strictly '1.2.23'
}
}
lockAllConfigurations()
only makes all of you configurations eligible for dependency locking. It does not automatically lock the dependencies. Otherwise the dynamic version will become useless and you can simply specify the exact version number when adding dependencies.
To actually produce the dependency lock file, you need to pass --write-locks to gradle when running a task, e.g.:
gradle dependencies --write-locks
See this part of the documentation.
I have a Gradle (v6.6.1) platform module that I publish to our internal maven repo. It's a constraints-only platform, and intended to be used by a large number of projects in my org so that we can standardize on versions of common dependencies.
So far it's working fine: I periodically review each dependency to look for an updated release, update those versions in my build.gradle.kts file, bump the version number of the platform itself (major or minor, depending on the changes), and publish that new platform to the maven repo.
However, doing so is a time-consuming and manual process. I'd much rather specify my version range constraints in build.gradle.kts (for example, latest patch version, or the latest minor version), and then use dependency locking to update the recommended version to for each dependency to a new specific version.
The problem is, Gradle seems to forbid this. Even when I use the following, it will only produce an empty api.lockfile:
dependencyLocking {
lockAllConfigurations()
}
task("resolveAndLockAll") {
doFirst {
require(gradle.startParameter.isWriteDependencyLocks,
{ "You must specify `--write-locks` to update the dependency version lock" })
configurations["api"].setCanBeResolved(true) // `api` not resolvable by default
}
doLast {
configurations.forEach {
println("Reviewing configuration ${it}...")
if (it.isCanBeResolved && it.name != "archives" && it.name != "default") {
it.resolve()
}
}
}
}
dependencies {
constraints {
api("com.google.guava:guava:${ property("guavaVersion") }")
api("com.google.inject:guice:${ property("guiceVersion") }")
... // many more
}
}
I think I understand the logic behind this: my platform only publishes constraints (not dependencies) so dependency locking doesn't apply. However, I want to specify exact constraints, allowing the consuming projects to jump from release to release of my platform project with exact versions recommended to them.
I also understand that those consuming projects can use dependency locking to get reproducible builds (and they do), but again, I want to specify exact constraints so that everyone on version, say, 17.4 of my platform is using exactly the same dependencies, and they can coordinate on issues they encounter.
Workaround(s)
The best idea I've got is to have a second, private module that defines the same set of dependencies (as actual dependencies) but with broader version ranges (e.g. [1.1, 2.0) instead of 1.4.3). I can use that module to generate a lock file, and then write a script that will update the platform's build.gradle.kts file with the newly-resolved specific dependency versions.
When using Firebase Performance in Android Studio the gradle task app:transformClassesWithFirebasePerformancePluginForDebug is taking significantly longer than any other task and is therefore dramatically slowing down my gradle build times.
Slow Build shown in Profiler
Firebase in our project caused 40% build time increase. To speed up debug builds we added a possibility to switch it on/off using build parameters in app/build.gradle and root build.gradle files:
app:
if (!project.hasProperty("disable-performance-plugin")) {
apply plugin: 'com.google.firebase.firebase-perf'
}
root/buildscript/dependencies:
if (!project.hasProperty("disable-performance-plugin")) {
classpath('com.google.firebase:firebase-plugins:1.1.5') {
exclude group: 'com.google.guava', module: 'guava-jdk5'
}
}
when running from the command line use
./gradlew your-task -Pdisable-performance-plugin
when working from Android Studio, add the flag to compiler options:
All of the existing answers are valid, but they all miss something.
To deal with this issue, you have 2 major options to choose from.
1. Use firebasePerformanceInstrumentationEnabled property
This is the official way provided by the SDK itself to disable it during the build process.
What this does:
Reduces transformClassesWithFirebasePerformancePluginFor* task execution time to ~5-10s.
Disables automatic traces and request monitoring, but leaves custom traces enabled. You can control the latter with AndroidManifest <meta-data> tags and calls to FirebasePerformance.getInstance().setPerformanceCollectionEnabled(). More info in the docs.
How to do this:
I think it's much easier to only enable plugin in those rare cases when we need it (usually it will be only when we publish the app) rather than disable it in all other cases.
Note: Of course, with manual builds you might forget to enable it. So if you don't have CI, it might be worth adding some other automatic scripting in Gradle, or sticking to the opposite approach that is used in other answers.
In general though, we only need two steps:
Add the following line to gradle.properties file:
firebasePerformanceInstrumentationEnabled=false
Use the following command in your CI config or manual builds:
./gradlew assembleRelease -PfirebasePerformanceInstrumentationEnabled=true
Pros:
Only one property to set up.
Cons:
Plugin still adds additional ~5-15s to the build time.
2. Use custom Gradle project property to avoid applying firebase-perf Gradle plugin
What this does:
transformClassesWithFirebasePerformancePluginFor* task is not executed at all. Also we save some additional ~5–10s overhead that is present when using the first solution.
Same as the first method – disables automatic traces and request monitoring, but leaves custom traces enabled. You can control the latter with AndroidManifest <meta-data> tags and calls to FirebasePerformance.getInstance().setPerformanceCollectionEnabled(). More info in the docs.
How to do this:
This approach has similar points and warnings, and also includes two steps:
Modify your app module's build.gradle file:
if (project.hasProperty('useFirebasePerf')) {
apply plugin: 'com.google.firebase.firebase-perf'
}
Note: you don't need to apply the same check to your project-level build.gradle:
classpath "com.google.firebase:firebase-plugins:$firebase_plugins_version"
This declaration won't be used in any way by Gradle when the plugin itself is not enabled.
And you don't need to exclude guava-jdk5 dependency there, if you're using firebase-plugins v1.1.1 or later as stated in the docs.
Use the following command in your CI config or manual builds:
./gradlew assembleRelease -PuseFirebasePerf
Pros:
Completely eliminates time expenses associated with Firebase Performance Gradle plugin.
Cons:
Introduces conditional check for applying plugin in your Gradle script, some might argue that it's not an idiomatic approach.
* (Bonus option) Use custom Gradle project property to exclude firebase-perf SDK
If you don't use custom traces or any other features from Firebase Performance SDK and only rely on automatic monitoring (that is, you don't have any dependencies on SDK in your code), then you can exclude this dependency for non-production builds.
How to do this:
All you need to do is update your app module's build.gradle file:
If you chose to use the first option, then change your dependency like this:
if (project.property('firebasePerformanceInstrumentationEnabled') == 'true') {
implementation "com.google.firebase:firebase-perf:${firebase_perf_version}"
}
If you chose the second one:
if (project.hasProperty('useFirebasePerf')) {
implementation "com.google.firebase:firebase-perf:${firebase_perf_version}"
}
Advantage:
This might save you some additional ~5-10s, spent on configuring dependency and
"ProGuarding" it.
Drawbacks:
Your production APK size will be larger than debug one by ~0.5mb. This might disrupt your reports or predictions, so you need to be aware of it.
If you were close to surpassing 64K method count limit, you might suddenly step over it on production builds and find yourself in the MultiDex zone. And that means extra work to do and tests to run. All because Firebase Performance brings a formidable number of almost 5K method references (after applying ProGuard with optimizations).
You can also check out my article where I expand a bit more on this topic.
Firebase Performance has released a new version of perf-plugin (v1.3.0). This would enable disabling the Firebase Performance Monitoring Gradle plugin for a specific build variant (including buildTypes or productFlavors).
An example below:
android {
// ...
debug {
FirebasePerformance {
// Set this flag to 'false' to disable #AddTrace annotation processing and
// automatic HTTP/S network request monitoring
// for a specific build variant at compile time.
instrumentationEnabled false
}
}
}
Reference to release notes:
https://firebase.google.com/support/release-notes/android#2019-07-10
All comments in this thread are valid. I want to suggest a very simple way to disable that for debug builds:
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Release")) {
apply plugin: 'com.google.firebase.firebase-perf'
}
For newer versions of the Firebase perf plugin (1.3.0 and up) with Kotlin DSL you'll need to add the following:
android {
...
buildTypes {
...
all {
with((this as ExtensionAware).extensions["FirebasePerformance"] as FirebasePerfExtension) {
setInstrumentationEnabled(!isDebuggable)
}
}
...
}
}
For the Groovy version you can check out the Firebase documentation.
Just to give another option to disable transformClassesWithFirebasePerformancePluginForDebug, here's my recipe:
In main build.gradle folder:
if (!project.gradle.startParameter.taskNames.any { taskName ->
taskName.toLowerCase().contains('assemble') && taskName.toLowerCase().contains('debug') }) {
classpath("com.google.firebase:firebase-plugins:$firebasePluginVersion") {
exclude group: 'com.google.guava', module: 'guava-jdk5'
}
}
In the build.gradle app file:
if (!project.gradle.startParameter.taskNames.any { taskName ->
taskName.toLowerCase().contains('assemble') && taskName.toLowerCase().contains('debug') }) {
apply plugin: 'com.google.firebase.firebase-perf'
}
I ran into this problem as well. Originally we had been using a variant of the answer provided by R. Zagórski, but based on a similar thread from the Gradle forums it seems like conditionally applying a plugin to a project isn't the right way to go:
Plugins can’t be applied to only “part of your project”. They are either applied or not.
Conditionally applying plugins does seem to work if you can do it right, but it's not an officially supported feature. Further down the same thread, another point is made:
But the plugin should allow you to configure it at a finer grained level.
Sure enough, there actually is a property exposed by the Firebase plugin that lets you toggle instrumentation on or off (therefore toggling the increased build time). Using this property is tricky, though, since you have to apply it at exactly the right time during the building process, but once you've got that then you can essentially pivot it on whatever you want.
The following code snippet is how we're pivoting instrumentation based on Debug vs. Non-Debug build variants. It's written in Kotlin, but I imagine it would translate to Groovy just as well:
plugins {
...
id ("com.google.firebase.firebase-perf")
}
...
android {
...
applicationVariants.all {
val variant = this
val isFirebaseEnabled = !variant.javaCompiler.name.contains("Debug", true)
gradle.taskGraph.whenReady {
if (this.hasTask(variant.javaCompiler))
{
project.FirebasePerformance.isInstrumentationEnabled = isFirebaseEnabled
}
}
}
...
}
Note that with this in place, the transformClassesWithFirebasePerformancePluginFor* task will still always run for every build variant, but it will complete almost immediately for a variant that doesn't have instrumentation enabled.
I think the clearest way is this with kotlin DSL gradle 👇
in app level build.gradle
buildTypes {
getByName(BuildType.DEBUG) {
extensions.configure<com.google.firebase.perf.plugin.FirebasePerfExtension>{
setInstrumentationEnabled(false)
}
}
}
in dependendencies part:
dependencies {
releaseImplementation(Dependencies.FIREBASE_PERFORMANCE) //implementation for only release mode (you can vary for other variants)
}
I have simplified option 2 of this answer https://stackoverflow.com/a/53270530/1635488
Define a property in gradle.properties
useFirebasePerf=false
Disable perf plugin
if (useFirebasePerf.toBoolean()) {
apply plugin: 'com.google.firebase.firebase-perf'
}
Remove dependency
if (useFirebasePerf.toBoolean()) {
implementation 'com.google.firebase:firebase-perf:16.2.3'
}
Enable Performance Monitoring only for CI builds (i would recommend only for release builds)
gradlew assembleRelease -PuseFirebasePerf=true
A much cleaner way in kotlin DSL
buildTypes {
//My custom extension
forName("debug") {
roject.ext.set("firebasePerformanceInstrumentationEnabled", "false")
}
}
Implementation of forName
fun <T> NamedDomainObjectContainer<T>.forName(name: String, action: T.() -> Unit) {
this.maybeCreate(name)
this.getByName(name, object: Action<T>{
override fun execute(t: T) {
t.action()
}
})
}