Indicating breaking changes in plugins with automated releases

I’m currently switching a plugin to JEP-229 automated releases, and need to decide on a versioning strategy for it. The plugin does not wrap a versioned component, so its version number is not meaningful. Due to the always-use-latest nature of Jenkins plugins, the only thing that needs to be conveyed to Jenkins users through versioning is whether the next plugin update is compatible or not.

I see two approaches for this…

Keep manual control of the major component

In this approach, breaking changes are signified by incrementing the major version component. For example

1.vabcdef456789    # no increment => compatible
2.aesf8989ase91   # major increment => breaking change

This has the advantage that whether you define plugins in plugins.txt (i.e. you’re a Jenkins Docker user) or you update through the Update Center, you can always visually see there’s been a major version bump.

Fully automated versioning + hpi.compatibleSinceVersion

In this approach the version number is fully automated, and we indicate a breaking change with the hpi.compatibleSinceVersion annotation. For example

efdcdef456789    # compatible
vesf8989ase91 + <hpi.compatibleSinceVersion>vesf8989ase91</hpi.compatibleSinceVersion> # breaking change

Originally this was only used to indicate configuration format changes, but I’m informed that it is now used for any breaking change.

This has the advantage that you get the incompatibility warning message displayed in the Update Center. The problem, as @timja has pointed out, is that Jenkins Docker users don’t see this message at all. They only find out that there’s a breaking change when the plugin update is already installed and it’s too late.

For this approach to be viable, there must be a way to surface hpi.compatibleSinceVersion warnings for Jenkins Docker users. If we can’t find a way to do this, I think we need to instead recommend that plugins with no meaningful version retain control of the major component and indicate breaking changes that way.

So… any suggestions on which direction to go?

Right, it’s basically the only tool we have to increase the chance that folks look at the changelog before updating. This is reflected by the increasingly generic wording shown in Jenkins now: jenkins/ at 0907d01d0a9e567b81dfca88ad854b5ef6dc9577 · jenkinsci/jenkins · GitHub

For this approach to be viable, there must be a way to surface hpi.compatibleSinceVersion warnings for Jenkins Docker users.

Seems like a limitation of jenkins-plugin-cli? While it doesn’t deal in “updates” (AFAIK), it could still show compatibility warnings published in metadata, at least if the threshold version is fairly recent.

By jenkins-plugin-cli do you mean GitHub - jenkinsci/plugin-installation-manager-tool: Plugin Manager CLI tool for Jenkins?

That tool does appear to have some kind of update handling:

java -jar jenkins-plugin-manager-*.jar --available-updates

If so, perhaps we could alter the way it checks for version updates. If a new version of a plugin is available AND its hpi.compatibleSinceVersion is higher than the specified version from plugins.txt, the warning is shown.

(For this to work, the fully automated version numbers of the format 123.vabcdef456789 need to be monotonic and easily comparable, so a newer version is always unambiguously higher than an older one. Are they?)

I also imagine this isn’t that different from what the manual Update Center does; is there any code we could reuse from that?


I filed Describing `hpi.compatibleSinceVersion` in CD-enabled plugins by jglick · Pull Request #5349 · jenkins-infra/ · GitHub to try to clear things up here.

The numeric component is the length of the history, so unless you force-push over the branch you release from (which you really should never do), it’ll be monotonic. It might be a bit annoying to “predict” the version that needs to be specified, but if, say, it’s about Pipeline: Groovy at version 2759.v87459c4eea_ca_, and the next PR will need to have this flag applied, you can probably just put 2760 and it’ll work.

Thanks for the update to the docs that clarifies this.

If a plugin has manual control of the major prefix, i.e. ${revision}.${changelist}, and the revision is bumped from 1 to 2, what does the generated changelist become in the first release of the 2.x line?

Does it go back to the start i.e. something like


Or does it continue counting the number of Git commits upwards e.g.


I just tested this locally on an example plugin…

commit f8c1f94dfacc9aafabf7f754368a85343fcd155f (HEAD -> main, tag: 2.3.vf8c1f94dfacc)
Date:   Fri Aug 12 13:47:14 2022 +0100

    Breaking change

commit cf52596f60d13c8bf4aeba3c75e2176f477219d8 (tag: 1.2.vcf52596f60d1)
Date:   Fri Aug 12 13:45:29 2022 +0100

    Trivial commit

commit dc24ee3f9a80078aac92a44e477741d1259ec2e7 (tag: 1.1.v9e7860a_67a_df)
Date:   Fri Aug 12 12:39:47 2022 +0100

    Initial commit

Note that because this example plugin is not hosted anywhere (it’s just on my computer), and does not have the actual CD process applied to it, the version number for Initial commit was fake - I copied it from an existing plugin, to get the versioning process started.

All subsequent version numbers were obtained by running mvn validate -Dset.changelist -Dignore.dirt, which should be a realistic reflection of what happens.

If the above is correct… it does seem to be that even when you bump the manually-controlled major version, the auto-generated part continues counting the Git commits upwards. (It does not revert back to .0.)

Therefore if you’re working on a plugin that manually controls the major version component, and you want to do a breaking change, when you set hpi.compatibleSinceVersion you’ll have to predict the new length of the Git history - just as if the plugin was using fully automated versioning.

So before committing Breaking change in the example above, you’d need to have set <hpi.compatibleSinceVersion>2.3</hpi.compatibleSinceVersion> in the POM.

Would appreciate someone confirming this on a real plugin though, if the case ever arises!

hpi.compatibleSinceVersion=2 should suffice in the above example. It does not need to be an actual release version, just something in the right range.