axion-release-plugin

repository·main·Indexed 20 days ago

https://github.com/allegro/axion-release-plugin

A Gradle plugin for automated release and version management that derives project versions from SCM tags instead of hardcoded values. It supports Semantic Versioning, monorepo configurations via include/exclude paths, and provides tasks like 'currentVersion' and 'release'. The plugin includes pre-release checks for uncommitted changes, remote synchronization, and snapshot dependencies, with support for SSH agent, netrc, and HTTP Basic Auth authorization.

Tokens
13.7K
Snippets
66
Records
71
Agent score
68%

What's inside axion-release-plugin

  1. Register release hooks in axion-release-plugin

    main

    You can register custom actions to be executed either before (pre) or after (post) the release process. Hooks are configured within the scmVersion block using the hooks property. You can provide either a predefined action name with arguments or a custom closure that accepts a HookContext object.

    scmVersion {
        hooks {
            pre { context -> /* custom logic */ }
            post { context -> /* custom logic */ }
        }
    }
  2. How axion-release-plugin manages versions

    main

    The plugin follows a philosophy where the SCM (Source Control Management) is the ultimate source of truth for the project version, rather than hardcoding it in build.gradle or pom.xml.

    Version Derivation Logic:

    • Release Version: If the current commit is a tagged commit, the project uses the version associated with that tag.
    • SNAPSHOT Version: If there are commits after the last tag, the project is considered to be in a SNAPSHOT state (e.g., 0.1.0-branch-SNAPSHOT).
    • Default: If no tags exist, it falls back to a default version.

    This approach works alongside Semantic Versioning to synchronize project versions with SCM tags.

  3. How next version markers work

    main

    By default, axion-release-plugin increments the patch version of the last released version. However, if you want to signal the start of work on a specific future version (e.g., moving from 1.5.1-SNAPSHOT directly to 2.0.0-SNAPSHOT), you can use a next version marker.

    A next version marker is a specific Git tag that ends with a customizable suffix. When the plugin detects a next version marker as the last tag, it treats that version as a SNAPSHOT but does not increment it. This allows you to jump to a major or minor version ahead of the current patch sequence.

    # 1. Current state
    # git tag: v1.5.0
    # ./gradlew cV -> 1.5.1-SNAPSHOT
    
    # 2. Mark next version as 2.0.0
    # git tag: v1.5.0 v2.0.0-alpha
    # ./gradlew cV -> 2.0.0-SNAPSHOT
    
    # 3. Subsequent normal release
    # ./gradlew cV -> 2.0.1-SNAPSHOT
  4. Authorize via SSH Agent or netrc

    main

    If no other authorization options are configured and the repository requires it, axion-release automatically attempts to use existing credentials from:

    • SSH Agent: ssh-agent on Linux or pageant on Windows.
    • HTTP/netrc: The <user-home>/.netrc file on Linux or <user-home>\_netrc on Windows.

    All interactions with the SSH agent are logged at info and debug levels.

  5. How Axion searches for tags: First encountered vs Highest version

    main

    Axion has two modes for determining the current version from the Git history:

    1. First tag encountered (Default): Axion starts from the current commit and walks up the commit tree until it finds the first tag matching the prefix. This is based on the history of the current commit, not necessarily the highest version in the entire repository.

    2. Tag with the highest version: Axion analyzes all commits from HEAD to the first commit to find the highest version number visible in the Git tree, regardless of the current branch's direct history.

    To enable the 'Highest version' mode, set useHighestVersion to true in your configuration or via the command line.

    scmVersion {
        useHighestVersion.set(true)
    }
  6. Use dry-run mode to preview release changes

    main

    Dry-run mode allows you to simulate a release without making any actual changes to the repository. In this mode, axion-release performs all read operations (like checking for uncommitted changes or branch status) but mocks all write operations (like creating tags or pushing to remote), printing what would have happened to the console instead.

    ./gradlew release -Prelease.dryRun
  7. Use local-only mode to skip remote interactions

    main

    Local-only mode prevents any actions that interact with a remote repository from being executed. This is useful for testing release logic without affecting the remote origin. You can enable this mode using either a command line flag or via the scmVersion configuration block. The command line flag takes precedence over the configuration setting.

    # Using the command line flag
    ./gradlew release -Prelease.localOnly
  8. Use axion-release-plugin with Gradle Kotlin DSL

    main

    As of v1.13.8, the plugin is compatible with Gradle's Kotlin DSL. Because the configuration is richly typed, you can benefit from IDE code completion using the following configuration objects:

    • VersionConfig
    • TagNameSerializationConfig
    • HooksConfig
    • RepositoryConfig
    • MonorepoConfig
    • NextVersionConfig
    • ChecksConfig

    Below is a comprehensive example demonstrating how to configure various elements including tags, repository settings, checks, next version logic, and lifecycle hooks.

    scmVersion {
        localOnly.set(true)
        useHighestVersion.set(true)
        tag {
            prefix.set("release")
            versionSeparator.set("/")
    
            // configure via function calls
            deserializer({ tagProperties, scmPosition, String -> "tag" })
            serializer({ tagProperties, version -> "tag" })
        }
        repository {
            type.set("git")
        }
        checks {
            aheadOfRemote.set(false)
            snapshotDependencies.set(true)
        }
        nextVersion {
            // function calls
            deserializer({ nextVersionProperties, scmPosition, tag -> "version" })
            serializer({ nextVersionProperties, version -> "version" })
        }
        hooks {
            pre({ println("here") })
            pre("commit") {
                println("here")
            }
    
            post({ println("here") })
            post("commit") {
                println("here")
            }
    
            preRelease {
                push()
                commit { releaseVersion, position -> "New commit message for version $releaseVersion" }
                custom { context -> println("$context") }
                fileUpdate {
                    file("README.md") // repeat for additional files
                    pattern = { previousVersion, context -> "version: $previousVersion" }
                    replacement = { currentVersion, context -> "version: $currentVersion" }
                }
            }
    
            postRelease {
                push()
                commit { releaseVersion, position -> "New commit message for version $releaseVersion" }
                custom { context -> println("$context") }
                fileUpdate {
                    file("README.md") // repeat for additional files
                    pattern = { previousVersion, context -> "version: $previousVersion" }
                    replacement = { currentVersion, context -> "version: $currentVersion" }
                }
            }
        }
        monorepo {
        }
    
        branchVersionIncrementer.putAll(
            mapOf<String, Any>(
                "master" to VersionProperties.Incrementer { c: VersionIncrementerContext -> c.currentVersion.incrementMajorVersion() }
            )
        )
    
        branchVersionCreator.putAll(
            mapOf(
                "master" to VersionProperties.Creator { s: String, scmPosition: ScmPosition -> "${s}-${scmPosition.branch}" }
            )
        )
    
        versionCreator({ versionFromTag, scmPosition -> "version" })
        snapshotCreator({ versionFromTag, scmPosition -> "version" })
        versionIncrementer({ versionIncrementerContext -> Version })
    }
  9. Basic workflow with axion-release

    main

    The axion-release-plugin manages Gradle project versions and releases by interacting with Git tags. The typical workflow involves checking the current version, performing development work (which increments the version to a -SNAPSHOT), executing a release to create a new Git tag, and then optionally marking the next version.

    Key Tasks in the Workflow:

    1. Check current version: Use ./gradlew currentVersion to see the version currently active in the project.
    2. Release: Run ./gradlew release to perform the release process (this typically creates a new Git tag).
    3. Publish: Use the standard Gradle ./gradlew publish task (provided by the maven-publish plugin) to publish the newly released version.
    4. Manual Version Marking: Use ./gradlew markNextVersion with the -Prelease.version property to explicitly set the next version as a -SNAPSHOT.
    # 1. Check current version
    ./gradlew currentVersion
    
    # 2. Perform a release
    ./gradlew release
    
    # 3. Publish the released version
    ./gradlew publish
    
    # 4. Manually mark the next version
    ./gradlew markNextVersion -Prelease.version=1.0.0
  10. Configure HTTP Basic Auth dynamically in Gradle

    main

    To provide HTTP credentials at runtime (e.g., fetching a GitHub token from a secure vault), use the scmVersion.repository.customUsername and scmVersion.repository.customPassword properties within a Gradle task that runs before the release.

    task loadGitHubToken << {
        scmVersion.repository.customUsername.set(loadGitHubTokenFromSomewhere())
    }
  11. Resolve dependency conflicts with JGit or JSch

    main

    axion-release-plugin depends on JGit and JSch. If other Gradle plugins in your build introduce version conflicts with these libraries, you can resolve them by explicitly excluding the conflicting group from the axion-release-plugin classpath and declaring the desired version directly in your buildscript block.

    To resolve a conflict with JGit, exclude org.eclipse.jgit from the plugin and then add the specific version you require as a separate classpath dependency.

    buildscript {
        dependencies {
            classpath("pl.allegro.tech.build:axion-release-plugin:<version>") {
                exclude group: "org.eclipse.jgit"
            }
            classpath("org.eclipse.jgit:org.eclipse.jgit:5.12.0.202106070339-r")
        }
    }