Refactor maven block in gradle - android

I'm working on an android project, and have a number of custom repositories using the same credentials:
repositories {
maven {
url "<url1>"
credentials {
username = "<username>"
password = "<password>"
}
}
maven {
url "<url2>"
credentials {
username = "<username>"
password = "<password>"
}
}
}
Is there a way to define a method (block?) so that I can define the username and password once and not have to repeat it every time? I'd like to be able to do:
repositories {
customMaven { url "<url1>"}
customMaven { url "<url2>"}
}
Apologies if I'm using terms incorrectly here - gradle syntax is somewhat of a mystery to me.

First answer provided by #ToYonos will work fine, but if you are looking for a solution based on a configuration block, you can use the method MavenArtifactRepository maven(Action<? super MavenArtifactRepository> action) from RepositoryHandler class (see here), as follows:
// a Closure that builds an Action for configuring a MavenArtifactRepository instance
def customMavenRepo = { url ->
return new Action<MavenArtifactRepository>() {
void execute(MavenArtifactRepository repo) {
repo.setUrl(url)
repo.credentials(new Action<PasswordCredentials>() {
void execute(PasswordCredentials credentials) {
credentials.setUsername("<username>")
credentials.setPassword("<password>")
}
});
}
};
}
// usage
repositories {
jcenter()
maven customMavenRepo("http://company.com/repo1")
maven customMavenRepo("http://company.com/repo2")
maven customMavenRepo("http://company.com/repo3")
}
EDIT from comments below: solution will Closure would look as follow. I think the use of curry method (see Currying closure) is needed here, but maybe there are other ways to simplify...
// one closure with URL as parameter
ext.myCustomMavenClosure = { pUrl ->
url pUrl
credentials {
username = "<username>"
password = "<password>"
}
}
// helper function to return a "curried" closure
Closure myCustomMaven (url){
return myCustomMavenClosure.curry(url)
}
repositories {
// use closure directly
maven myCustomMavenClosure.curry ("http://mycompany.com/repo1")
// or use helper method
maven myCustomMaven("http://mycompany.com/repo2")
}

Gradle is all about this kind of customization :
repositories {
['<url1>', '<url2>'].each { u ->
maven {
url u
credentials {
username = "<username>"
password = "<password>"
}
}
}
}

Related

Reading Gradle properties in settings.gradle.kts

So I'm using Gradle Kotlin DSL, I want to know if it's possible to read gradle properties inside settings.gradle.kts?
I have gradle.properties file like this:
nexus_username=something
nexus_password=somepassword
I've done it like this, but still can't read the properties.
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
maven { setUrl("https://developer.huawei.com/repo/") }
maven { setUrl("https://jitpack.io") }
maven {
setUrl("https://some.repository/")
credentials {
val properties =
File(System.getProperty("user.home")+"\\.gradle", "gradle.properties").inputStream().use {
java.util.Properties().apply { load(it) }
}
username = properties["nexus_username"].toString()
password = properties["nexus_password"].toString()
}
}
}
}
You can access values set in gradle.properties in both build.gradle.kts and settings.gradle.kts using delegate properties (Kotlin DSL only, because delegate properties is a Kotlin feature!).
gradle.properties
kotlin.code.style=official
# Your values here
testValue=coolStuff
build.gradle.kts
val testValue: String by project
settings.gradle.kts
val testValue: String by settings
You can access gradle parameters using providers (since 6.2)
val usernameProvider = providers.gradleProperty("nexus_username")
val passwordProvider = providers.gradleProperty("nexus_password")
dependencyResolutionManagement {
repositories {
maven {
setUrl("https://some.repository/")
credentials {
username = usernameProvider.getOrNull()
password = passwordProvider.getOrNull()
}
}
}
}
To work on Groovy, you need to replace the variable declaration with:
def usernameProvider = providers.gradleProperty("nexus_username")
def passwordProvider = providers.gradleProperty("nexus_password")
Based on answer

gradle kotlin dsl: how to create a shared function which uses a plugin class?

A simplified child module build.gradle.kts:
plugins {
id("com.android.library")
kotlin("android")
}
android {
androidComponents.beforeVariants { it: com.android.build.api.variant.LibraryVariantBuilder ->
it.enabled = run {
// logic to calculate if
it.productFlavors[0].second == "flavor" && it.buildType == "debug"
}
}
}
Is it possible to extract function for calculation of enabled state of buildVariant?
fun calculateIsEnabled(lvb: com.android.build.api.variant.LibraryVariantBuilder): Boolean {
return lvb.productFlavors[0].second == "flavor" && lvb.buildType == "debug"
}
I tried to declare the function in the root build.gradle.kts but I don't know how to access it from submodule and if it is possible at all
I tried to declare it in buildSrc module, but com.android.build.api.variant.LibraryVariantBuilder is undefined here because the plugin com.android.library is not present here and I think it is not allowed and/or meaningless
So, the question is: where to declare a shared function that uses types defined in a gradle plugin and need to be accessible in all submodules of type android library?
After several tries I solved it:
buildSrc/build.gradle.kts
repositories {
google()
mavenCentral()
}
plugins {
`kotlin-dsl`
}
dependencies {
// important: dependency only in simple string format!
implementation("com.android.tools.build:gradle:7.2.0-alpha03")
}
buildSrc/src/main/kotlin/Flavors.kt
import com.android.build.api.variant.LibraryVariantBuilder
import com.android.build.api.variant.ApplicationVariantBuilder
private fun isFlavorEnabled(flavor1: String, buildType: String): Boolean {
return flavor1 == "flavor" && buildType == "debug"
}
fun isFlavorEnabled(lvb: LibraryVariantBuilder): Boolean {
// productFlavors are pairs of flavorType(dimension) - flavorName(selectedFlavor)
return lvb.run { isFlavorEnabled(productFlavors[0].second, buildType ?: "") }
}
fun isFlavorEnabled(avb: ApplicationVariantBuilder): Boolean {
return avb.run { isFlavorEnabled(productFlavors[0].second, buildType ?: "") }
}
In library/build.gradle.kts and app/build.gradle.kts
android {
androidComponents.beforeVariants {
it.enabled = isFlavorEnabled(it)
}
}

Android - gradle script or function for duplicate maven project repository template

In my top-level build.gradle file for my Android project, I have to import several maven project repositories. The structure looks like this:
allprojects {
repositories {
google()
jcenter()
// How can I avoid this duplicate code block?
maven {
url "https://my_maven_project_url"
credentials(HttpHeaderCredentials) {
def jobToken = System.getenv("CI_JOB_TOKEN")
if (jobToken == null || jobToken.empty) {
name = "Private-Token"
value = "myAccessToken"
} else {
name = "Job-Token"
value = jobToken
}
}
authentication {
header(HttpHeaderAuthentication)
}
}
url "https://my_maven_project_url" and "myAccessToken" are the 2 things I need to pass in for each project. Can I write a script or a function that basically does the maven { ... } portion as a template and I just pass those 2 values in for each project?
The issue is that I think in a function we would need to create the maven { ... } portion programmatically instead of in the template form like shown above, but I may be understanding incorrectly...
You can write method like this
ext.myMethod = { param1, param2 ->
// Method body here
}
and call method
myMethod('param1', 'param2')

How to write a custom mavenArtifactRepositoryContainer?

In my Android project I am maintaining the URL to a local Maven server in a few places in build.gradle files:
buildscript {
repositories {
maven { url "https://example.com/nexus/content/groups/com.example.android.public/" }
}
}
I also maintain dependencies in a Kotlin class within the buildSrc folder:
object Plugins {
private object Versions {
const val android = "3.4.2"
}
const val android = "com.android.tools.build:gradle:${Versions.android}"
}
Is there a way I can define a mavenCompany variable in the buildSrc Kotlin file and invoke it from the build.gradle file(s) in my modules? Basically, I want the same as mavenLocal(). One of the related files is org.gradle.api.artifacts.ArtifactRepositoryContainer which defines a few constants:
public interface ArtifactRepositoryContainer extends
NamedDomainObjectList<ArtifactRepository>,
Configurable<ArtifactRepositoryContainer> {
String DEFAULT_MAVEN_CENTRAL_REPO_NAME = "MavenRepo";
String DEFAULT_MAVEN_LOCAL_REPO_NAME = "MavenLocal";
String MAVEN_CENTRAL_URL = "https://repo.maven.apache.org/maven2/";
String GOOGLE_URL = "https://dl.google.com/dl/android/maven2/";
I could not figure out how to write my own.

NoSuchMethodError using Google Endpoints

I am creating an Android app using Google App Engine. In order to use GCM (Google Cloud Messaging), I have created a GCM module in Android Studio. This module provides a sample code that registers devices in the Datastore.
All was working well yesterday, and although nothing changed, I have this error when I try to register my device :
java.lang.NoSuchMethodError: com.google.appengine.api.datastore.Cursor: method <init>()V not found
I don't know what exactly means the notation <init>()V, but I found the Cursor class to be generated by the Google plugin of Android Studio, here :
This is the decompiled code inside Cursor.class :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.google.appengine.api.datastore;
import java.io.Serializable;
public final class Cursor implements Serializable {
private String webString;
public Cursor(String webString) {
this.webString = webString;
}
public String toWebSafeString() {
return this.webString;
}
public static Cursor fromWebSafeString(String encodedCursor) {
if(encodedCursor == null) {
throw new NullPointerException("encodedCursor must not be null");
} else {
return new Cursor(encodedCursor);
}
}
public boolean equals(Object o) {
if(this == o) {
return true;
} else if(o != null && this.getClass() == o.getClass()) {
Cursor cursor = (Cursor)o;
return this.webString.equals(cursor.webString);
} else {
return false;
}
}
public int hashCode() {
return this.webString.hashCode();
}
public String toString() {
return this.webString;
}
}
Finally, this is my build.gradle :
// If you would like more information on the gradle-appengine-plugin please refer to the github page
// https://github.com/GoogleCloudPlatform/gradle-appengine-plugin
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.appengine:gradle-appengine-plugin:1.9.18'
}
}
repositories {
jcenter();
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.18'
compile 'com.google.appengine:appengine-endpoints:1.9.18'
compile 'com.google.appengine:appengine-endpoints-deps:1.9.18'
compile 'javax.servlet:servlet-api:2.5'
compile 'com.googlecode.objectify:objectify:4.0b3'
compile 'com.ganyo:gcm-server:1.0.2'
}
appengine {
downloadSdk = true
appcfg {
oauth2 = true
}
endpoints {
getClientLibsOnBuild = true
getDiscoveryDocsOnBuild = true
}
}
Because I changed nothing in the concerned code, I really can't understand what happened and I found nothing useful on the web.
Thank you in advance for your help.
Edit : StackTrace from the backend log
It's looking for the empty constructor : method init()v not found
It appears this could be because Cursor.java is pulled from your <module>/build/classes/main (or <module>/build/exploded-app/WEB-INF/classes/main), when really it should just be pulled in from a library appengine-api-1.0-sdk-<version>.jar.
Have you added the source for a Cursor.java into your project src folder somehow? The App Engine build creates a runnable build at <module>/build/exploded-app and the cursor class is usually sourced from <module>/build/exploded-app/WEB-INF/lib/appengine-api-1.0-sdk-<version>.jar

Categories

Resources