9

As already asked in Gradle function to define custom maven repository? I want to define named functions for the repositories block. They should configure common (but enterprise internal) repositories.

We have a custom gradle distribution where these functions should be defined via init.d script.

Even though the code from the mentioned question works for groovy scripts, it does not for *.gradle.kts.

I was hoping to be able to use Kotlin extensions, and they work very well inside the initialization script:

// init.d/repositories.gradle.kts

fun RepositoryHandler.buildRepo() {
    maven {
        url =  java.net.URI(System.getenv("MAVEN_BUILD_REPO_URL"))
        credentials {
            username = System.getenv("MAVEN_BUILD_REPO_URL")
            password = System.getenv("MAVEN_BUILD_REPO_URL")
        }
    }
}

allprojects {
    repositories {
        buildRepo()
    }
}

Unfortunately these extensions are gone as soon as one tries to use them in the actual projects build file:

repositories {
    buildRepo()
}

For build.gradle files the error is:

Could not find method buildRepo() for arguments [] on repository container of type org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.

For build.gradle.kts it's a compilation failure:

Script compilation error:

  Line 3:     buildRepo()
              ^ Unresolved reference: buildRepo

I hope someone has a idea of how to fix that.

dag
  • 1,133
  • 12
  • 25

2 Answers2

1

Introduction

Gradle loads separately and in strict order the following scripts:

  • init.gradle(.kts)
  • settings.gradle(.kts)
  • build.gradle(.kts)

Separately means that different class loaders or different processes can be used. Therefore, in order to have any method or class, it must be added to the classpaths of these scripts. And if Kotlin DSL is used, code completion will be achieved.

The solution is to create a library containing the Kotlin extension and include it in the classpathes of these scripts.

Building a library

So for this we have a library containing only one class without any package:

// SomeFileName.kt

import org.gradle.api.artifacts.dsl.RepositoryHandler

fun RepositoryHandler.myRepo() {
    apply {
        mavenCentral() // replace with your own repository
    }
}

This library should be published to your own repository. To demonstrate that this works, I used the Maven Local repository. The build.gradle.kts for this library looks like this:

plugins {
    kotlin("jvm") version "1.9.0"
    `java-library`
    `maven-publish`
}

group = "my.company"
version = "1.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation(gradleApi())
}

publishing {
    publications {
        create<MavenPublication>("gradleExtensions") {
            from(components["java"])
        }
    }

    repositories {
        mavenLocal() // replace with your own repository like Nexus or Artifactory
    }
}

Patching init.gradle.kts

The library can be included using the initscript block:

// init.d/init.gradle.kts

initscript {
    repositories {
        gradlePluginPortal() // needed for some purposes
        mavenLocal() // replace with your own repository
    }

    dependencies {
        classpath("my.company:gradle-extensions:1.0")
    }
}

And the myRepo() method can now be called:

// init.d/init.gradle.kts

allprojects {
    repositories {
        myRepo()
    }
}

Pathcing build.gradle.kts

This file can be patched using the buildscript block. And this block can be defined both in the script itself and in init.gradle.kts.

// build.gradle.kts

buildscript {
    repositories {
        mavenLocal() // replace with your own repository
    }

    dependencies {
        classpath("my.company:gradle-extensions:1.0")
    }
}

And in the init script it looks like this:

// init.d/init.gradle.kts

allprojects {
    buildscript {
        repositories {
            mavenLocal() // replace with your own repository
        }

        dependencies {
            classpath("my.company:gradle-extensions:1.0")
        }
    }
}

And the myRepo() method can now be called:

// build.gradle.kts

repositories {
    myRepo()
}

Patching settings.gradle.kts

The settings script also has its own buildscript that can be configured in the same manner.

For example in the init script it looks like this:

// init.d/init.gradle.kts

beforeSettings {
    buildscript.apply {
        repositories {
            gradlePluginPortal()
            mavenLocal()
        }

        dependencies.apply {
            add(ScriptHandler.CLASSPATH_CONFIGURATION, "my.company:gradle-extensions:1.0")
        }
    }
}

But unfortunately it doesn't work.

// settings.gradle.kts

pluginManagement {
    repositories {
        myRepo() // error: "Unresolved reference: myRepo"
    }
}

I don't know if this is a bug or if I did something wrong. I didn't find any useful information.

But there is a workaround.

Instead of setting myRepo() in the settings script, it can also be set globally in the init script, like this:

// init.d/init.gradle.kts

initscript {
    repositories {
        gradlePluginPortal() // needed for some purposes
        mavenLocal() // replace with your own repository
    }

    dependencies {
        classpath("my.company:gradle-extensions:1.0")
    }
}

beforeSettings { 
    pluginManagement { 
        repositories { 
            myRepo()
        }
    }
}

I hope this works for you.

I also published working code on my github

Maksim Eliseev
  • 487
  • 1
  • 11
0

I couldn't find an elegant solution, but the code below works:

// init.d/init.gradle.kts

class MyRepoProvider(private val handler: RepositoryHandler) {
    operator fun invoke() {
        handler.apply {
            mavenCentral() // replace with your own repository
        }
    }
}

allprojects {
    repositories {
        extra.set("myRepo", MyRepoProvider(this))
    }
}

And in the build.gradle.kts it looks like this (DO NOT BE AFRAID):

repositories {
    myRepo.invoke() // :scream:
}

Because there is no information about that the myRepo object has an operator function.

Maksim Eliseev
  • 487
  • 1
  • 11
  • I appreciate the self-awareness that this solution is inelegant, but it does seem to work. `extra` can be abused so easily. I stumbled on a solution that's a bit more elegant than this yesterday, that involves building a custom gradle distribution with an init.d/ file that applies a plugin to the classpath in `buildscript` blocks in settings.gradle.kts and project. That allows you to use real identifiers, but unfortunately it does not work in the pluginManagement { ... } block in settings.gradle.kts, which is a shame. Going to wait to see if anyone else posts a solution before awarding bounty. – Mike Holler Aug 24 '23 at 16:01