qoomon/gradle-git-versioning-plugin

Request: replace string-based templating with lambda parameters, so I can programatically create the version

Opened this issue ยท 12 comments

aSemy commented

At the moment the version is constructed based on a static string template. The plugin will do a find/replace in the version string for format placeholders, and replace them with existing format placeholder values.

However I would like to dynamically select the version, based on the format placeholder values. For example:

If CODEBUILD_WEBHOOK_TRIGGER is not available, then use the 'ref' placeholder.

version = "0.0.0-SNAPSHOT"
gitVersioning.apply {
    refs {
        branch(".+") {
            // if the env-var is unavailable, use the 'ref'
            version = "\${commit.timestamp.datetime}-\${env.CODEBUILD_WEBHOOK_TRIGGER.slug:-\${ref}}"
        }
    }
    rev { version = "\${commit.timestamp.datetime}-\${commit}" }
}

This resolves to

20220425.112958-${ref}

But I want it to be

20220425.112958-feat-add-widget

Suggestion

Perhaps the find/replace can be made more intelligent, but I'd recommend an alternative. Remove the string templating, and provide the git variables as parameters to the existing action.

public void branch(String pattern, Action<RefPatchDescription> action) {
RefPatchDescription ref = new RefPatchDescription(BRANCH, Pattern.compile(pattern));
action.execute(ref);
this.list.add(ref);
}

        public void branch(String pattern, Action<GitProperties, RefPatchDescription> action) {
            RefPatchDescription ref = new RefPatchDescription(BRANCH, Pattern.compile(pattern));
            // action.execute(ref); // don't execute immediately, evaluate it later, once the git properties are determined
            this.list.add(action);
        }
        
         // when required, evaluate the version
        public RefPatchDescription evaluateVersion() {
            for (var Action<> action in list) {
                return action.invoke(gitProperties)
            }
        }

(I'm not sure on the correct Action class to use, but in this case GitProperties would be provided as an argument and the Action must return a RefPatchDescription.

In build.gradle.kts it would be used something like this

version = "0.0.0-SNAPSHOT"
gitVersioning.apply {
    // List of ref configurations, ordered by priority. First matching configuration will be used.
    refs {
        branch(".+") { gitProperties ->
            var gitRef = gitProperties.env("CODEBUILD_WEBHOOK_TRIGGER")
            if (gitRef.isNullOrBlank()) gitRef = gitProperties.ref()
            version = "${gitProperties.commit.timestamp.datetime}-${gitRef}"
        }
    }
    rev { gitProperties -> 
      version = "${gitProperties.commit.timestamp.datetime}-${gitProperties.commit}" }
}

Other points

A quick question, rather than making a ticket just for for it, I am confused by this line

- `refs` List of ref configurations, ordered by priority.

Is it ordered by ascending priority, as in the first definition will be overridden by later definition, if they match?

Hi @aSemy , this is a very interesting idea. This plugin is a clone of the maven git versioning extension, that is why it has static templating, however I really like your approach it is much more gradle like. I'll try to implement it this way and release a new major version within the next month

regarding your other point, โ„น First matching configuration will be used. from top to bottom. Does that help?

@aSemy I haven't found a way to implement your idea with the Action class nor any alternative. Do you have an clue for a solution?

aSemy commented

Ah I thought the Action class was a significant Gradle object, but thinking about it, it's not. It just needs to be a lambda parameter. In Kotlin it would be

fun branch(pattern: String, action: (GitProperties) -> RefPatchDescription) {
  this.list.add(action)
}

I forget what the Java interface is, but you could always define your own. I think it would be something like this.

@FunctionalInterface
interface RefPatchDescriptionProvider {
  public RefPatchDescriptor action(GitProperties gitProperties)
}

...

public void branch(String pattern, RefPatchDescriptionProvider action) {
  this.list.add(action);
}

I think that will be fine, and would be compatible with the Gradle task avoidance way of working.

If it would help out, I can try and implement it, but I'm really not a fan of Java any more, so I'd like to do it in Kotlin. Would you be interested in converting the project to Kotlin? At least the Gradle plugin element.

@aSemy I'm totally on your side i switched to Kotlin some years ago also :-), however I was too lazy to convert this project. So yes I am interested in converting it. One Requirement would be that it will still works within groovy gradle files.

I'll try to implement it in Java and then I'll migrate the project to kotlin

your approach would work, however it would lead to a ugly syntax like

branch(".+") { gitSituation ->
      new PatchDescription( version = "${gitSituation.ref}")
 }
aSemy commented

Ah I see... I'll see if I can make a draft PR as a demo.

I think using @HasImplicitReceiver is the start of improving it

import me.qoomon.gitversioning.commons.GitSituation;
import me.qoomon.gradle.gitversioning.GitVersioningPluginConfig.RefPatchDescription;
import org.gradle.api.HasImplicitReceiver;

@FunctionalInterface
@HasImplicitReceiver
interface RefPatchDescriptionProvider {
    void action(RefPatchDescription description, GitSituation gitSituation);
}

in Gradle this would be used like this

branch(".+") { gitSituation ->
  version = "${gitSituation.ref}")
}

I think this can probably make it work. But thinking about it this situation is probably a good fit for Gradle's 'NamedDomainObjectContainers' https://docs.gradle.org/current/userguide/custom_gradle_types.html#collection_types. But that can be done much later.

aSemy commented

I'll try to implement it in Java and then I'll migrate the project to kotlin

hey @qoomon, how would you feel about picking this up again? :) I'd like to help out. I can start by converting the Gradle config to Kotlin?

@aSemy oh that would be awesome.

aSemy commented

I have done some pondering and I think the best way forward is to essentially create a new version by changing gradle-git-versioning-plugin to be a Gradle Settings plugin.

https://stackoverflow.com/questions/69149466/gradle-7-2-how-to-apply-a-custom-gradle-settings-plugin

abstract class GradleGitVersionPlugin : Plugin<Settings> { }

This has some advantages.

  1. the git version can be determined before the projects are configured
  2. the version only has to be configured once, and will be immutable
  3. the settings plugin can apply an extension to all subprojects that will contain the computed version

I also think that confusing bit of how a version is selected #71 (comment) can be made more clear by making it conditional when the version is selected. Instead of having a refs {} block, the plugin should provide a sealed-class with three subtypes (attached, detached, unknown), so users can do an exhaustive when {}.

@aSemy sounds great I was not aware of Gradle Settings plugins