Customize selecting dependency version - gradle

in my project dependency which I'm using has many similar version names, eg: 1.0.0, 1.0.0-dev, 1.0.0-dev2... Is there a way to list all versions starting with 1.0.0 and select interesting version from that list?
I was thinking about resolutionStrategy, but it doesn't contain list of possible versions

You could do this
configurations.all.resolutionStrategy {
List<DependencyResolveDetails> drdList = []
eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'foo' && details.requested.name = 'bar') {
drdList << details
}
}
if (drdList.size() > 1) {
List<String> versionOptions = drdList*.requested*.version
String selectedVersion = selectBestVersion(versionOptions) // TODO: implement
drdList.each { DependencyResolveDetails details ->
if (details.requested.version != selectedVersion) {
details.useVersion(selectedVersion).because("I picked $selectedVersion using a custom strategy")
}
}
}
}
Perhaps you could create a plugin for this so that applying a custom strategy for a group/name is a bit cleaner possibly by registering a Comparator<String> for a group/name combination
Eg:
apply plugin: 'x.y.custom-version-strategy'
Comparator<String> customVersionComparator = (String version1, String version2) -> { ... }
customVersionStrategy {
strategy 'foo', 'bar', customVersionComparator
}

It looks like, that finnaly I have found a solution:
configurations.all{
resolutionStrategy {
componentSelection {
all { ComponentSelection selection ->
if(notInteresting(selection.candidate.version))
selection.reject("")
}
}
}
}

Related

TransformerAction is not triggered on dependency resolution

I would like to transform some dependencies when Gradle resolves them.
I have followed the 'Transforming dependency artifacts on resolution' documentation, but my TransformAction is not triggered.
I have
created a custom configuration, gitHub
created custom attributes, and both
applied them to the gitHub configuration,
and registered them in the dependencies {} block)
created and registered a custom TransformAction.
created a task, resolveGitHubDependencies, that will resolve the gitHub configuration.
I have tried a few different adjustments, but nothing seems to have an impact.
What am I missing? Or is this a bug?
Here's the code I've created. It can be tested by running ./gradlew resolveGitHubDependencies. I expect that MyTransformer will print some log statements, but it doesn't print anything.
import org.slf4j.LoggerFactory
plugins {
base
}
repositories {
ivy("https://github.com/") {
name = "GitHub"
patternLayout {
artifact("/[organisation]/[module]/archive/[revision].[ext]")
artifact("/[organisation]/[module]/releases/download/[revision]/[module](-[revision]).[ext]")
}
metadataSources { artifact() }
}
}
// from https://github.com/gradle/gradle/issues/19707
val transformed = Attribute.of("my-library.transformed", Boolean::class.javaObjectType)
val dependencySource = Attribute.of("dependency source", String::class.java)
// my custom configuration - I want to transform all dependencies from this configuration
val gitHub by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(dependencySource, "gitHub")
attribute(transformed, false)
}
}
// this is the transformer that I expect to be triggered on dependency resolution
abstract class MyTransformer : TransformAction<MyTransformer.Parameters> {
private val logger = LoggerFactory.getLogger(this::class.java)
interface Parameters : TransformParameters {
#get:Input
val sourceAttributes: MapProperty<Attribute<*>, Any?>
}
#get:PathSensitive(PathSensitivity.NAME_ONLY)
#get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override fun transform(outputs: TransformOutputs) {
val sourceAttributes = parameters.sourceAttributes.get()
logger.error("sourceAttributes: $sourceAttributes")
logger.error("sourceAttributes: ${sourceAttributes.keys}")
logger.error("sourceAttributes: ${sourceAttributes.values.joinToString { it.toString() }}")
val input = inputArtifact.get().asFile
outputs.file(input)
}
}
dependencies {
// this is the dependency I want to transform
gitHub("emcrisostomo:fswatch:1.17.1#tar.gz")
attributesSchema {
attribute(dependencySource)
attribute(transformed)
}
// artifactTypes.configureEach {
// attributes.attribute(transformed, false)
// }
registerTransform(MyTransformer::class) {
// from.attributes.attribute(dependencySource, "gitHub")
// to.attributes.attribute(dependencySource, "gitHub")
from.attributes.attribute(dependencySource, "gitHub").attribute(transformed, false)
to.attributes.attribute(dependencySource, "gitHub").attribute(transformed, true)
parameters {
sourceAttributes.set(
from.attributes.keySet().associateWith { key ->
from.attributes.getAttribute(key)
}
)
}
}
}
val resolveGitHubDependencies by tasks.registering(Sync::class) {
group = "gh transform"
from(gitHub)
into(temporaryDir)
}
Gradle version: 7.6

Declare Gradle buildSrc plugin using Kotlin DSL

I'm trying to figure out how to convert this configuration to the Kotlin DSL, but I can't find much in the way of examples:
gradlePlugin {
plugins {
javaConventionsPlugin {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
What would this declaration look like using Kotlin?
It is documented in the guide: https://docs.gradle.org/current/userguide/java_gradle_plugin.html#sec:gradle_plugin_dev_usage
The way you have also works. Any of the following would also work:
gradlePlugin {
plugins {
register("javaConventionsPlugin") {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
gradlePlugin {
plugins {
create("javaConventionsPlugin") {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}
}
The former uses Gradle's lazy configuration.
This is what I've found so far, not sure if there's a more fluent way to do it:
gradlePlugin {
val javaConventionsPlugion = plugins.register("javaConventionsPlugin")
javaConventionsPlugion.configure {
id = "build.java-conventions"
implementationClass = "buildlogic.plugins.JavaConventionsPlugin"
}
}

Kotlin build FatFramework with defined minimum iOS version

I want to deploy my multiplatform project to .framework with supported iOS architecture using FatFramework using this gradle configuration. It's work but I founded in info.plist that it have default MinimumOSVersion to iOS 9 so I can't used my library below that version.
Is there any way / possible to configure the minimum iOS version for my lib ?
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
plugins {
id("org.jetbrains.kotlin.multiplatform") version "1.3.61"
id("maven-publish")
id("com.jfrog.bintray") version "1.8.4"
id("org.jetbrains.dokka") version "0.10.0"
}
group = "com.example.lib"
version = "1.0.0"
repositories {
mavenCentral()
jcenter()
}
kotlin {
jvm()
val iosX64 = iosX64("ios")
val iosArm64 = iosArm64("iosArm64")
val iosArm32 = iosArm32("iosArm32")
val frameworkName = "EXAMPLE-LIB"
configure(listOf(iosX64, iosArm64, iosArm32)) {
binaries.framework {
baseName = frameworkName
}
}
sourceSets {
named("commonMain") {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(kotlin("stdlib-jdk7"))
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.0-RC")
implementation(kotlin("reflect"))
}
}
named("commonTest") {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(kotlin("reflect"))
}
}
named("jvmMain") {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
named("jvmTest") {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val iosMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
val iosArm64Main by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
val iosArm32Main by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.0-RC")
implementation("io.ktor:ktor-client-ios:1.2.6")
}
}
}
val debugFatFramework by tasks.creating(FatFrameworkTask::class) {
baseName = frameworkName
from(
iosArm32.binaries.getFramework("debug"),
iosArm64.binaries.getFramework("debug"),
iosX64.binaries.getFramework("debug")
)
destinationDir = buildDir.resolve("fat-framework/debug")
group = "Universal framework"
description = "Builds a debug universal (fat) framework"
}
val releaseFatFramework by tasks.creating(FatFrameworkTask::class) {
baseName = frameworkName
from(
iosArm32.binaries.getFramework("release"),
iosArm64.binaries.getFramework("release"),
iosX64.binaries.getFramework("release")
)
destinationDir = buildDir.resolve("fat-framework/release")
group = "Universal framework"
description = "Builds a release universal (fat) framework"
}
val zipDebugFatFramework by tasks.creating(Zip::class) {
dependsOn(debugFatFramework)
from(debugFatFramework)
from("LICENSE.md")
}
val zipReleaseFatFramework by tasks.creating(Zip::class) {
dependsOn(releaseFatFramework)
from(releaseFatFramework)
from("LICENSE.md")
}
}
tasks.register<Exec>("generateThriftModels") {
executable = "java"
args(
"-jar", "thrifty-compiler.jar",
"--lang", "kotlin",
"--omit-generated-annotations",
"--list-type=kotlin.collections.ArrayList",
"--set-type=kotlin.collections.LinkedHashSet",
"--out", "src/commonMain/kotlin",
"--path", "./thrift/",
"./thrift/nativeapp.thrift"
)
}
This parameter is derived from the Kotlin/Native's compiler properties list. It can be found there:~/.konan/kotlin-native-prebuilt-<hostname>-<version>/konan/konan.properties. The value responsible for the MinimumOSVersion is the osVersionMin.<target_name>. This should be set per target. It can be changed manually, and this new value will be used by all your projects. Also, after 1.4.30 there is a new way to specify compiler properties. See in this document for example. I would recommend using it as follows:
...
configure(listOf(iosX64, iosArm64, iosArm32)) {
binaries.framework {
freeCompilerArgs += listOf("-Xoverride-konan-properties=osVersionMin.ios_arm32=7;osVersionMin.ios_arm64=7;osVersionMin.ios_x64=7")
}
}
...

Handling multi project builds with Grade, Jacoco and Sonarqube

This is working so far (at least it looks like it works), but I don't feel that it is idiomatic or would stand the test of time even for a few months. Is there any way to do it better or more "by the book"?
We have a few multi project builds in Gradle where a project's test could touch another one's code, so it is important to see the coverage even if it wasn't in the same project, but it was in the same multiproject. I might be solving non existing problems, but in earlier SonarQube versions I had to "jacocoMerge" coverage results.
jacocoTestReport {
reports {
executionData (tasks.withType(Test).findAll { it.state.upToDate || it.state.executed })
xml.enabled true
}
}
if(!project.ext.has('jacocoXmlReportPathsForSonar')) {
project.ext.set('jacocoXmlReportPathsForSonar', [] as Set<File>)
}
task setJacocoXmlReportPaths {
dependsOn('jacocoTestReport')
doLast {
project.sonarqube {
properties {
property 'sonar.coverage.jacoco.xmlReportPaths', project.
ext.
jacocoXmlReportPathsForSonar.
findAll { d -> d.exists() }.
collect{ f -> f.path}.
join(',')
}
}
}
}
project.rootProject.tasks.getByName('sonarqube').dependsOn(setJacocoXmlReportPaths)
sonarqube {
properties {
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.tests", []
property "sonar.junit.reportPaths", []
}
}
afterEvaluate { Project evaldProject ->
JacocoReport jacocoTestReportTask = (JacocoReport) evaldProject.tasks.getByName('jacocoTestReport')
evaldProject.ext.jacocoXmlReportPathsForSonar += jacocoTestReportTask.reports.xml.destination
Set<Project> dependentProjects = [] as Set<Project>
List<String> configsToCheck = [
'Runtime',
'RuntimeOnly',
'RuntimeClasspath',
'Compile',
'CompileClasspath',
'CompileOnly',
'Implementation'
]
evaldProject.tasks.withType(Test).findAll{ it.state.upToDate || it.state.executed }.each { Test task ->
logger.debug "JACOCO ${evaldProject.path} test task: ${task.path}"
sonarqube {
properties {
properties["sonar.junit.reportPaths"] += task.reports.junitXml.destination.path
properties["sonar.tests"] += task.testClassesDirs.findAll { d -> d.exists() }
}
}
configsToCheck.each { c ->
try {
Configuration cfg = evaldProject.configurations.getByName("${task.name}${c}")
logger.debug "JACOCO ${evaldProject.path} process config: ${cfg.name}"
def projectDependencies = cfg.getAllDependencies().withType(ProjectDependency.class)
projectDependencies.each { projectDependency ->
Project depProj = projectDependency.dependencyProject
dependentProjects.add(depProj)
}
} catch (UnknownConfigurationException uc) {
logger.debug("JACOCO ${evaldProject.path} unknown configuration: ${task.name}Runtime", uc)
}
}
}
dependentProjects.each { p ->
p.plugins.withType(JacocoPlugin) {
if (!p.ext.has('jacocoXmlReportPathsForSonar')) {
p.ext.set('jacocoXmlReportPathsForSonar', [] as Set<File>)
}
p.ext.jacocoXmlReportPathsForSonar += jacocoTestReportTask.reports.xml.destination
JacocoReport dependentJacocoTestReportTask = (JacocoReport) p.tasks.getByName('jacocoTestReport')
dependentJacocoTestReportTask.dependsOn(jacocoTestReportTask)
setJacocoXmlReportPaths.dependsOn(dependentJacocoTestReportTask)
}
}
}

How to mix ResolutionStrategies with Gradle

Let's say I have the following in my gradle build script:
configurations.all {
resolutionStrategy {
failOnVersionConflict()
force 'com.google.guava:guava:18.0'
}
}
This will fail if more than one version of a jar is found, except for guava where it will force to version 18.0.
Now let's imagine that I want to have failOnVersionConflict() for all external jars, and be forced to use the force clause (so I know what I'm doing), but I want to use the default resolutionStrategy (newest version) for some specific group, like com.mycompany.
Is such a thing possible?
I was looking at this documentation page:
https://gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html
I found my own answer... But it involves a bit of "hacking"... But, after all, that's just what gradle offers...
def dependencyErrors = 0
configurations.all {
resolutionStrategy {
def thirdPartyPackages = [:]
def forced = [
'com.google.guava:guava' : '18.0'
//Here all forced dependencies...
]
eachDependency { DependencyResolveDetails details ->
if (!details.requested.group.startsWith('com.mycompany')) {
def key = details.requested.group + ":" + details.requested.name
if(!thirdPartyPackages.containsKey(key)) {
if(forced.containsKey(key)) {
details.useVersion forced.get(key)
}
else {
thirdPartyPackages.put(key, details.requested.version);
}
}
else {
def existing = thirdPartyPackages.get(key);
if(existing != details.requested.version) {
logger.error "Conflicting versions for [$key]"
logger.error " [$existing]"
logger.error " [$details.requested.version]"
dependencyErrors++
}
}
}
}
}
}
myTask.doFirst {
//here it might also be doLast, or whatever you need. I just put it before a war task, but it might depend on each need.
if(dependencyErrors > 0) {
ant.fail 'There are ' + dependencyErrors + ' conflicting jar versions in the build.'
}
}

Resources