Bnd Kotlin DSL Draft

Dear bnd community,

TL;DR
First of I didn’t pick the typical way of education to become a developer. I started off as a gardener interested in programming. Please be patient, if my level of knowledge is uncomplete.

I started at a small company planning to move to another platform. During dev internal meetings I heard conversations about Spring Boot, but it seems like leaders are very uncertain about which direction they should take. Of course my personal favorite is OSGi.

Unfortunately they doesn’t know all too much about it. To make them ever consider OSGi for future projects, migration has to be as easy as possible and I have to kickstart it on my own.

Thank god there is bnd, which abstracts away almost all the hard lifting - from assembly up to provisioning. The code generation part is perfectly covered.

The next thing which came into mind is code creation: IDE support. Intellij (read on) has good support for Gradle Kotlin DSL which provides in-place documentation out-of-the-box through code completion. Luckily there are language server projects aiming Kotlin as well, which are becoming better and better. Some of which have experimental Gradle Kotlin DSL support.

So the road I am thinking of is: Gradle Kotlin DSL -> bnd Kotlin DSL -> bndlib.

I started experimenting with a small project which maps basic bnd operations to a bnd string/script.

Showcase 1 - Basic Bnd Kotlin DSL

val instructions = bnd {
    includeDefaults()
    definePlugins()
    defineConnections() // intented to setup connection settings but if I could directly use repositories provided by Gradle this would be obsolete
    defineRepositories()
    bndToolsTemplate(
            type = MyConstants.Bndtools.Template.PROJECT,
            name = project.name,
            category = "Bnd Kotlin DSL samples"
    ) {
        println("Run additional configuration logic in here")
        println("This closure is valid but not necessary")
        icon(project.file("resources/icon.png")) // Gradle's project instance
    }

    importPackage {
        pattern("org.jetbrains.kotlin.*")
        pattern("*")
        patterns += "*"                     // possible as well
        optional = true
    }

    bndPomRepository {
        releaseUrl += repositories.mavenCentral().artifactUrls
    }

    mavenBndRepository {
        poll_time = 4
        releaseUrl += "https://repo.maven.apache.org/maven2/"
        snapshotUrl += releaseUrl
        label = "maven.central"
        order = 0
    }
}

Showcase 2 - Extensibility through extension functions:

/**
 * following extension function would normally sit in a organization specific plugin
 */

fun Instructions.includeDefaults() {
    include(URI("http://etcd.mycompany.local/build/env"))
    include(rootProject.file("cnf/build.bnd"))
}

fun Instructions.defineConnections() {
    connectionSettings {
        maven()
        bnd()
        server {
              // custom server settings
        }
    }
    include(rootProject.file("cnf/build.bnd"))
}

fun Instructions.definePlugins() {
    springComponent {
        println("Configure SpringComponent plugin")
        order = 100
    }
}

fun Instructions.defineRepositories() {
    val mycompanyrepo = mavenBndRepository("MyCompanyRepo") {
        releaseUrls += project.repositories.get("mycompanyrepo").artifactUrls
        order = 0
    }
    releaseRepo(mycompanyrepo)
    baselineRepo(mycompanyrepo)
}

/**
 * Extension functions which could be provided by 3rd party plugins
 */
fun Instructions.bndtoolsTemplate(
        type: String = "project",
        name: String = "Bndtools Template",
        category: String = "templates"
): BndtoolsTemplateInstruction {
    val instance = BndtoolsTemplateInstruction()
    instance.type = type
    instance.name = name
    instance.category = category
    return instance
}

fun Instructions.bndtoolsTemplate(
        type: String = "project",
        name: String = "Bndtools Template",
        category: String = "templates",
        configuration: BndtoolsInstruction.()->Unit
) {
    val instance = bndtoolsTemplate(type, name, category)
    configuration.invoke(instance)
    return instance
}

Simply press ALT+TAB and voilá, You got documentation out of the box. Even with this simple instruction set, it’s already a joy to use. :blush:

But
At the moment these Instruction implementations share an print(PrintConfiguration) method returning the generated String which feels like back and forth when looking (superficially) at bndlib internals: I am serializing the build logic so that bndlib can deserialize it again!

Kotlin is a strongly typed language and as such, mapping to a dynamic language (which I consider bnd is) can sometimes be cumbersome.

Regarding direct access to classes in Gradle, classloader magic comes into mind.

Conclusion
I am really sorry, I don’t know that much about the bnd workflow. Up until now I had my projects prepopulated by templates which were just building good and I never questioned it. But now I am in front of a project, which needs deeper / more complex understandings of bnd.

Having good IDE (I like VS Code setup headless as a remote HTTP server connecting to my electron client app as well) support is a personal itch to me, even if my company won’t move to OSGi.

I am not asking You to do my work, but sometimes listening to experts helps alot, before walking 200km into the wrong direction.

Much thanks in advance

Adrian

BTW This is my first try getting involved into a open source project :blush:

One of my design goals of bnd (formerly btool) was absolute simplicity. I looked at DSLs but picked the Properties format because anybody can read a properties file. More important, property files are the most simple format to name values. Also, a properties file is 100% declarative.

I do understand that this is constraining, believe me. However, I believe that these constraints force you to solve the problems in a simpler way, which in my experience reduces many problems downstream. A friend once told me that he did not mind constraints. There is the same amount of space between a 0 and 1mm as there is between zero and 1 mile: infinite. I think constraints also give you more compatibility because there is less variation. With LEGO, you can reuse the same building blocks because they constrained the connectors and size.

I picked the single workspace because it is simpler than for example the Maven model of doubly linked poms. Not only is it simpler, there is a humongous class of errors that can happen in a doubly linked list that are impossible with the container model of the bnd workspace. Interestingly, many Maven projects nowadays seem to resemble the bnd workspace. However, due to their doubly linked poms are much more complex and error prone. It is, probably because it maps well to git.

In my work, one of my struggles is always to reduce variation. Most developers love new technologies. However, having worked with thousands of developers over 45 year I too often have seen that cool language become a maintenance nightmare. It worked well when the original author was there, it worked well for another 2 years but then one day it fails and nobody knows Scala anymore. Yes, Java sucks on many levels but at least it is the sucky language everybody knows and hates. And imho, I really do not see that the language makes that much of a difference in productivity. Same for Eclipse/Intellij. Most of the developers I work with do not use 1% of the features of either.

So I am sadly not motivated to go in this direction. That said, bnd is an open tent. Although the bndlib was originally intended to not have a public API, I lost that battle long ago. And if we need certain hooks to make your code work, I am more than willing to collaborate. However, if you have cycles to spare, I’d 1000x more would like you to work on adding features to the bndtools Intellij plugin from Bram Pouwelse, or even better for me the bndtools Eclipse :slight_smile:

Anyway, good luck!