diff --git a/.editorconfig b/.editorconfig index 86bd861..8c5ffb2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,12 +12,14 @@ tab_width = 4 [*.md] max_line_length = 80 +[{*.bash,*.sh,*.zsh}] +indent_size = 2 + [{*.yaml,*.yml}] indent_size = 2 [*.{kt,kts}] ktlint_standard_filename = disabled ktlint_standard_annotation = disabled -ktlint_standard_function-signature = disabled -ktlint_standard_multiline-expression-wrapping = disabled -ktlint_standard_string-template-indent = disabled +ktlint_standard_value-argument-comment = disabled +ktlint_standard_value-parameter-comment = disabled diff --git a/.github/workflows/autolabeler.yaml b/.github/workflows/autolabeler.yaml index 6eeef29..d417a42 100644 --- a/.github/workflows/autolabeler.yaml +++ b/.github/workflows/autolabeler.yaml @@ -2,7 +2,7 @@ name: PR Autolabeler on: pull_request: - types: [opened, edited, reopened, synchronize] + types: [ opened, edited, reopened, synchronize ] branches-ignore: - 'renovate/**' diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/gradle-build.yaml similarity index 100% rename from .github/workflows/ci-build.yaml rename to .github/workflows/gradle-build.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/gradle-release.yaml similarity index 96% rename from .github/workflows/release.yaml rename to .github/workflows/gradle-release.yaml index facc364..d0c51f0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/gradle-release.yaml @@ -79,7 +79,8 @@ jobs: run: | ./gradlew \ -Psemver.modifier=${{ github.event.inputs.version-modifier }} \ - build ${{ steps.build_parameters.outputs.publishCommand }} \ + build \ + ${{ steps.build_parameters.outputs.publishCommand }} \ ${{ steps.build_parameters.outputs.githubReleaseCommand }} \ -Psigning.keyId=${{ secrets.OSSRH_GPG_SECRET_KEY_ID }} \ -Psigning.password=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} \ diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml new file mode 100644 index 0000000..8b3c858 --- /dev/null +++ b/.github/workflows/publish-docs.yaml @@ -0,0 +1,46 @@ +name: Publish Docs +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.x + + - name: Set cache id + run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + + - name: Configure Cache + uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + + - name: Install pipx + run: python -m pip install --upgrade pipx + + - name: Install mkdocs + run: pipx install mkdocs-material --include-deps + + - name: Deploy to GitHub Pages + run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index 645864b..2b3d364 100644 --- a/.gitignore +++ b/.gitignore @@ -1,100 +1,24 @@ -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/ - -# File-based project format *.iws - -# IntelliJ +*.iml +*.ipr out/ -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -### Gradle template -.gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Avoid ignore Gradle wrappper properties -!gradle-wrapper.properties - -# Cache of project -.gradletasknamecache - -# Eclipse Gradle plugin generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -### macOS template -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent +**/.gradle/** +**/build/** -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Kotlin template -# Compiled class file *.class - -# Log file -*.log - -# BlueJ files *.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.nar *.ear -*.zip -*.tar.gz +*.log +*.nar *.rar +*.tar.gz +*.war +*.zip -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +.DS_Store +.gradletasknamecache +.idea/** hs_err_pid* replay_pid* +.kotlin/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 208b330..4c42782 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,34 +1,21 @@ # Contributing -👋 Thanks for wanting to contribute! -We are always looking for bug fixes, changes, and new features. -If you'd like to work on something please consider making an issue to help track what is being worked on, but someone from the Figure team will be around to help review code and approve pull requests! +👋 Thanks for wanting to contribute! +We are always looking for bug fixes, changes, and new features. +If you'd like to work on something please consider making an issue to help track what is being worked on, but someone +from the Figure team will be around to help review code and approve pull requests! ## Working Locally To build locally: ``` -./gradlew clean build +./gradlew build ``` -To publish locally we recommend commenting out the `signing` plugin, located in the `build-logic/build-conventions/src/main/kotlin/local.publishing.gradle.kts`. -This stops the signing from happening on a publish locally so you don't need to mess around with keys. -Then you can run: - -``` -./gradlew publishToMavenLocal -``` - -## Tests - -Currently we don't have a lot of tests, but we do have one set up that uses the `GradleRunner` to create a gradle environment just for the test that we can then apply our code to and run tasks. -Check out the `BuildLogicFunctionalSpec` to see how this works. - ## Licensing One thing we need to ensure is that we maintain a license header on each source file. We have automated this through the use of a gradle plugin! -Whenever a `.gradlew build` is run, the license headers will automatically be regenerated. Please make sure to commit these updates. - -More info about this plugin is available on github: https://github.com/CadixDev/licenser +Whenever a `./gradlew build` is run, the license headers will automatically be regenerated. Please make sure to commit +these updates. diff --git a/HEADER.txt b/HEADER.txt deleted file mode 100644 index 983118e..0000000 --- a/HEADER.txt +++ /dev/null @@ -1,4 +0,0 @@ -Copyright (c) ${year} ${company} and its affiliates. - -This source code is licensed under the Apache 2.0 license found in the -LICENSE.md file in the root directory of this source tree. diff --git a/README.md b/README.md index 4ad5495..8bdd9cd 100644 --- a/README.md +++ b/README.md @@ -1,242 +1,82 @@ -# Gradle Semver Plugin - -This plugin adds a flexible approach to adding semantic versioning to your gradle project using git history. -It supports multi-module gradle projects, running in CI, and multiple types of git strategies. - -- [Usage](#usage) -- [Overview](#overview) -- [Using with CI](#using-with-ci) -- [Version Calculation](#version-calculation) -- [Branch Matching Strategy](#branch-matching-strategy) -- [Unsupported](#not-supported) - -## Usage +# Semver Gradle Plugin + +This Semver Gradle plugin provides a simple approach to +adding semantic versioning to your gradle project using git +history regardless of git strategies. + +At a glance, this plugin provides the following features: + +- Support for stages (`rc`, `beta`, `stable`, `snapshot`, etc. - see below) +- Support for modifiers (`auto`, `patch`, `minor`, `major`) +- Support for branch-based version calculations +- Support for overriding the version +- Support for setting an alternate initial version +- Support for specifying alternate main and development branch names +- Support for appending build metadata (format: `+`) +- Build support when + - No git repository is present + - No git tags are present + - No remote branch is present + - Merging, rebasing, cherry-picking, bisecting, reverting, or in a detached + head state + +## Installation + +The following can be added to any of the following: + +- `settings.gradle.kts` (recommended) + - This will automatically apply the version to all projects +- `build.gradle.kts` (root project) + - This will only automatically apply the version to the root project +- `build.gradle.kts` (subproject) + - This will only automatically apply the version to the subproject + +If the semantic version is targeting the entire project, it's recommended to add +this to the `settings.gradle.kts` file. ```kotlin plugins { - id("com.figure.gradle.semver-plugin") version "" + id("com.figure.gradle.semver") version "" } - -// The semver extension must be declared before invoking semver.version -semver { - // All properties are optional, but it's a good idea to declare those that you would want - // to override with Gradle properties or environment variables, e.g. "overrideVersion" below - tagPrefix("v") - initialVersion("0.0.1") - findProperty("semver.overrideVersion")?.toString()?.let { overrideVersion(it) } -} - -// must be called after semver {} -version = semver.version ``` -If you're using a Gradle Version Catalog, feel free to use these entries: +## Configuration -```toml -[versions] -figure-gradle-semver = "" - -[plugins] -gradle-semver = { id = "com.figure.gradle.semver-plugin", version.ref = "figure-gradle-semver" } -``` +> [!IMPORTANT] +> The most minimal configuration is to not provide any configuration at all. +> This will use the default settings and will generate a version based on the +> git history. +> +> However, configurations exist to allow for more control over the versioning +> calculation process. ```kotlin -plugins { - alias(libs.plugins.gradle.semver) -} -``` - -## Overview - -Whenever a gradle task is ran, such as `./gradlew clean build`, the semver plugin will calculate the current semantic -version based on git history. -This calculation is done using: - -- The version of the target branch -- The current branch -- The branch matching strategy - -This calculated semantic version is then available as an output with the extension properties `semver.version` -and `semver.versionTagName`. - -### Glossary +// For older versions of gradle, you may need to import the configuration method +import com.figure.gradle.semver.semver -| Item | Definition | Example | -|--------------------------------|----------------------------------------------------------------------------------------|-----------------------------| -| _current branch_ | The branch you are working on. | `fix-bug` | -| _target branch_ | The branch that the current branch targets, often the default branch. | `main`, `master`, `develop` | -| _latest version_ | The latest published git tag on the target branch. | `1.0.2` | -| _current / calculated version_ | The version that is calculated when it runs. This will be ahead of the latest version. | `1.0.3` | - -### Plugin Extension Properties - -These variables come from the plugin extension, and are only available after the `semver {}` extension is configured. - -| Variable | Type | Description | -|------------------|----------|----------------------------------------------------------------------------------------------| -| `version` | `String` | The current version, e.g. `1.0.1` | -| `versionTagName` | `String` | The tag name for the current calculated version, i.e. `tagPrefix` + `version`, e.g. `v1.0.1` | - -Example: - -```kotlin +// This is purely for example purposes semver { - // ... -} -version = semver.version - -``` - -### Plugin Tasks - -| Task Name | Description | -|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `currentSemver` | Print the current `version` and `versionTagName` | -| `generateVersionFile` | Generate the `build/semver/version.txt` file containing the raw version and the tag version, often used in CI | -| `createAndPushVersionTag` | Create a git tag from `semver.versionTagName` and push the tag to the remote repo. Be careful using `:createAndPushVersionTag` in a multi module project as it will attempt to create duplicate tags for each project. | - -## Using with CI - -When using this plugin in CI, ensure that all branches & tags are checked out to get an accurate version calculation. -By default, the `actions/checkout` action only pulls the latest commit, which can cause some issues with this plugin. - -GitHub Actions Example: - -```yaml -- name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 # <-- This config is the most important part, and checks out the entire history of the repo -``` + // Default: `settings.settingsDir` + rootProjectDir = settingsDir.parent -## Version Calculation + // Default: `0.0.0` (first build will generate `0.0.1`) + initialVersion = "1.0.0" -This plugin comes bundled with a single Version Calculator that implements a target branch calculator - the version of -the current -branch is based on the latest version of the branch it targets, e.g. `develop` is branched from `main`, so the version -of `develop` is based on the current version of `main`. + // No "default", but the plugin will search in order for: + // `main`, `master + mainBranch = "trunk" -The Target Branch Version Calculator includes two Branch Matching strategies, based on the git strategy that is being -used. -By default, the `Flow` strategy is selected if a `develop` branch is present, otherwise the `Flat` strategy will be -used. + // No "default", but the plugin will search in order for: + // `develop`, `devel`, `dev` + developmentBranch = "development" -- `Flat` - Ideal for projects using a single branch strategy with `main` or `master`. - -| branch | pre release label | target branch | example | -|---------------------------|-------------------|----------------|--------------| -| `main` or `master` | | main or master | 1.2.3 | -| `rc/my-release-candidate` | rc | main or master | 1.2.4-rc.2 | -| `xxx` | xxx | main or master | 1.2.4-xxx.13 | - -- `Flow` - Broadly based on a [Git Flow workflow](https://nvie.com/posts/a-successful-git-branching-model/) without - release branches, the following branches are supported: - -| branch | pre release label | target branch | example | -|---------------------------|-------------------|----------------|---------------| -| `main` or `master` | '' | main or master | 1.2.3 | -| `develop` | beta | main or master | 1.2.4-beta.13 | -| `rc/my-release-candidate` | rc | main or master | 1.2.4-rc.2 | -| `xxx` | xxx | develop | 1.2.5-xxx.13 | - -### Branch Matching Detection Order - -The following outlines the branch matching detection order from first matched to last matched: - -1. Custom version strategy -2. `main` and `develop` exist -3. `master` and `develop` exist -4. `main` only exists -5. `master` only exists -6. Throw unsupported branching strategy - -## Branch Matching Strategy - -A Strategy contains a list of `BranchMatchingConfiguration` instances which are applied in order until the first match -is reached, it contains the following properties: - -- Branch name regex -- Target branch -- Version modifier: modifies the major, minor or patch components of the semver -- Version qualifier: optionally qualifies the semver with a prerelease label and build metadata - -The `VersionModifier` can be set for all `BranchMatchingConfiguration` instances in the strategy with the plugin -extension: - -```kotlin -semver { - versionModifier { nextPatch() } - // OR - versionModifier("patch") -} -``` - -Only a single `BranchMatchingConfiguration` whose regex matches the current branch will be applied, so effectively this -sets the `VersionModifier` for the current branch. - -The supported values are `major`, `minor` and `patch`. - -## Advanced Usage - -```kotlin -plugins { - id("com.figure.gradle.semver-plugin") version "" -} - -// The semver extension must be declared before invoking semver.version -semver { - // All properties are optional, but it's a good idea to declare those that you would want - // to override with Gradle properties or environment variables, e.g. "overrideVersion" below - tagPrefix("v") - initialVersion("0.0.3") - findProperty("semver.overrideVersion")?.toString()?.let { overrideVersion(it) } - - // This is only used for non-user defined strategies, i.e. predefined Flow or Flat - findProperty("semver.modifier")?.toString() - ?.let { versionModifier(buildVersionModifier(it)) } - - // Manually specifying the gitDir location is typically not necessary. However, in cases where you have a composite - // gradle build, it will become necessary to define where your .git directory is in correlation to your composite - // build. In the following example, you may have a build at `parent/child`. `child` specifies that the parent - // directory to its projectDir should contain the `.git` directory. - gitDir("${rootProject.projectDir.parent}/.git") -} - -version = semver.version -``` - -Using a custom Strategy (not currently supported by the configuration cache): - -```kotlin -semver { - // All properties are optional, but it's a good idea to declare those that you would want - // to override with Gradle properties or environment variables, e.g. "overrideVersion" below - tagPrefix("v") - initialVersion("0.0.3") - findProperty("semver.overrideVersion")?.toString()?.let { overrideVersion(it) } - val semVerModifier = findProperty("semver.modifier")?.toString() - ?.let { buildVersionModifier(it) } ?: { nextMinor() } - - versionCalculatorStrategy( - FlatVersionCalculatorStrategy(semVerModifier) - ) - // OR from scratch - this is rarely used now that Flat and Flow support anything - .* - versionCalculatorStrategy( - listOf( - BranchMatchingConfiguration("""^main$""".toRegex(), GitRef.Branch.Main, { "" to "" }, semVerModifier), - BranchMatchingConfiguration( - """.*""".toRegex(), - GitRef.Branch.Main, - { preReleaseWithCommitCount(it, GitRef.Branch.Main, it.sanitizedNameWithoutPrefix()) to "" }, - semVerModifier - ), - ) - ) + // Default: `never` + // Options: `never`, `always`, `locally` + appendBuildMetadata = "locally" } ``` -_**PLEASE NOTE:**_ the `semver` extension should be declared **before** the `semver` extension functions are used. - -## Not Supported +## Documentation -- Separate versions for subprojects - all subprojects are calculated with the same version +For more detailed documentation, please +visit [figuretechnologies.github.io/gradle-semver-plugin](https://figuretechnologiesgithub.io/gradle-semver-plugin). diff --git a/api/gradle-semver-plugin.api b/api/gradle-semver-plugin.api new file mode 100644 index 0000000..2273f84 --- /dev/null +++ b/api/gradle-semver-plugin.api @@ -0,0 +1,29 @@ +public final class com/figure/gradle/semver/Constants { + public static final field INSTANCE Lcom/figure/gradle/semver/Constants; + public static final field SEMVER_PROPERTY_PATH Ljava/lang/String; +} + +public abstract interface class com/figure/gradle/semver/SemverExtension { + public static final field Companion Lcom/figure/gradle/semver/SemverExtension$Companion; + public static final field NAME Ljava/lang/String; + public abstract fun getAppendBuildMetadata ()Lorg/gradle/api/provider/Property; + public abstract fun getDevelopmentBranch ()Lorg/gradle/api/provider/Property; + public abstract fun getInitialVersion ()Lorg/gradle/api/provider/Property; + public abstract fun getMainBranch ()Lorg/gradle/api/provider/Property; + public abstract fun getRootProjectDir ()Lorg/gradle/api/file/RegularFileProperty; +} + +public final class com/figure/gradle/semver/SemverExtension$Companion { + public static final field NAME Ljava/lang/String; +} + +public final class com/figure/gradle/semver/SemverExtensionKt { + public static final fun semver (Lorg/gradle/api/initialization/Settings;Lkotlin/jvm/functions/Function1;)V +} + +public final class com/figure/gradle/semver/SemverPlugin : org/gradle/api/Plugin { + public fun ()V + public synthetic fun apply (Ljava/lang/Object;)V + public fun apply (Lorg/gradle/api/plugins/PluginAware;)V +} + diff --git a/build-logic/build-conventions/build.gradle.kts b/build-logic/build-conventions/build.gradle.kts index 3976768..96216c3 100644 --- a/build-logic/build-conventions/build.gradle.kts +++ b/build-logic/build-conventions/build.gradle.kts @@ -2,16 +2,9 @@ plugins { `kotlin-dsl` } -repositories { - gradlePluginPortal() -} - dependencies { listOf( - libs.gradle.plugin.publish, - libs.ktlint, - libs.detekt, - libs.licenser, + libs.gradle.plugin.publish ).forEach { implementation(it) } diff --git a/build-logic/build-conventions/src/main/kotlin/local.analysis-conventions.gradle.kts b/build-logic/build-conventions/src/main/kotlin/local.analysis-conventions.gradle.kts deleted file mode 100644 index 4a9a743..0000000 --- a/build-logic/build-conventions/src/main/kotlin/local.analysis-conventions.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -import io.gitlab.arturbosch.detekt.Detekt -import io.gitlab.arturbosch.detekt.extensions.DetektExtension -import org.jlleitschuh.gradle.ktlint.KtlintExtension - -plugins { - id("io.gitlab.arturbosch.detekt") - id("org.jlleitschuh.gradle.ktlint") -} - -configure { - buildUponDefaultConfig = true - allRules = false - config = files("${rootDir.path}/detekt.yml") - source = files("**/kotlin/**") -} - -tasks.withType().configureEach { - reports { - html.required.set(true) - xml.required.set(false) - txt.required.set(false) - sarif.required.set(false) - } -} - -configure { - debug.set(false) - verbose.set(true) - android.set(false) - outputToConsole.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(false) - filter { - exclude("**/generated/**") - include("**/kotlin/**") - } -} diff --git a/build-logic/build-conventions/src/main/kotlin/local.licenser.gradle.kts b/build-logic/build-conventions/src/main/kotlin/local.licenser.gradle.kts deleted file mode 100644 index cc4fa4b..0000000 --- a/build-logic/build-conventions/src/main/kotlin/local.licenser.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -import org.cadixdev.gradle.licenser.tasks.LicenseUpdate -import java.util.Calendar - -plugins { - // https://github.com/CadixDev/licenser - id("org.cadixdev.licenser") -} - -license { - header(project.file("HEADER.txt")) - - // use /** for kotlin files - style.put("kt", "JAVADOC") - - // This is kinda weird in kotlin but the plugin is groovy so it works - properties { - this.set("year", Calendar.getInstance().get(Calendar.YEAR)) - this.set("company", "Figure Technologies") - } - - include("**/*.kt") // Apply license header ONLY to kotlin files -} - -// Ensure licenses are updated when the app is assembled -// This needs to happen early in the gradle lifecycle or else the checkLicenses task fails -tasks.named("assemble") { - dependsOn("updateLicenses") -} - -tasks.withType().configureEach { - notCompatibleWithConfigurationCache("Does not work") -} diff --git a/build-logic/build-conventions/src/main/kotlin/local.publishing.gradle.kts b/build-logic/build-conventions/src/main/kotlin/local.publishing.gradle.kts index 6657b3c..4275772 100644 --- a/build-logic/build-conventions/src/main/kotlin/local.publishing.gradle.kts +++ b/build-logic/build-conventions/src/main/kotlin/local.publishing.gradle.kts @@ -1,91 +1,81 @@ plugins { - id("com.gradle.plugin-publish") // java-gradle-plugin and maven-publish included + id("com.gradle.plugin-publish") if ("CI" in System.getenv()) { signing } } -tasks.withType().configureEach { - notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/13470") -} - -tasks.withType().configureEach { - notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/13470") -} - -/* - * Project information - */ -group = "com.figure.gradle" -description = "Gradle Plugin for Automated Semantic Versioning" - -inner class ProjectInfo { +class PublishingConstants { val name = "Gradle Semver Plugin" - val description = "Gradle Plugin for Automated Semantic Versioning" - val pluginImplementationClass = "$group.semver.SemverPlugin" - val tags = listOf("semver", "git semver", "versioning") - val website = "https://github.com/FigureTechnologies/gradle-semver-plugin" + val description = "Gradle Plugin for Automatic Semantic Versioning" + val pluginImplementation = "com.figure.gradle.semver.SemverPlugin" + val tags = listOf("semver", "versioning", "git") + + val website = "https://figuretechnologies.github.io/gradle-semver-plugin" val vcsUrl = "https://github.com/FigureTechnologies/gradle-semver-plugin.git" val scmUrl = "scm:git:git://github.com/FigureTechnologies/gradle-semver-plugin.git" } -val info = ProjectInfo() -// specific section for gradle portal -gradlePlugin { - website.set(info.website) - vcsUrl.set(info.vcsUrl) +val info = PublishingConstants() + +configure { + website = info.website + vcsUrl = info.vcsUrl plugins { - create(project.name) { - id = "$group.${project.name}" + create("semver-plugin") { + id = "$group.semver-plugin" displayName = info.name description = info.description - tags.set(info.tags) - implementationClass = info.pluginImplementationClass + implementationClass = info.pluginImplementation + tags = info.tags } } } afterEvaluate { - publishing { - publications.filterIsInstance().forEach { - it.pom { - name.set(info.name) - description.set(info.description) - licenses { - license { - name.set("The Apache Software License, Version 2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") - distribution.set("repo") - } - } - developers { - developer { - id.set("figure-oss") - name.set("Figure OSS Engineers") - email.set("oss@figure.com") + configure { + publications { + withType { + pom { + name = info.name + description = info.description + licenses { + license { + name = "The Apache Software License, Version 2.0" + url = "https://www.apache.org/licenses/LICENSE-2.0.txt" + distribution = "repo" + } } - developer { - id.set("ahatzz11") - name.set("Alex Hatzenbuhler") - email.set("ahatzenbuhler@figure.com") + developers { + developer { + id = "figure-oss" + name = "Figure OSS Engineers" + email = "oss@figure.com" + } + developer { + id = "tcrawford-figure" + name = "Tyler Crawford" + email = "tcrawford@figure.com" + } + developer { + id = "ahatzz11" + name = "Alex Hatzenbuhler" + email = "ahatzenbuhler@figure.com" + } + developer { + id = "jonasg13" + name = "Jonas Gorauskas" + email = "jgorauskas@figure.com" + } } - developer { - id.set("tcrawford") - name.set("Tyler Crawford") - email.set("tcrawford@figure.com") + scm { + connection = info.scmUrl + developerConnection = info.scmUrl + url = info.website } - developer { - id.set("jonasg13") - name.set("Jonas Gorauskas") - email.set("jgorauskas@figure.com") - } - } - scm { - connection.set(info.scmUrl) - developerConnection.set(info.scmUrl) - url.set(info.website) } } } } } + diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index cee3d7e..252242c 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1,16 +1,12 @@ rootProject.name = "build-logic" -// This is for the kotlin-dsl dependencyResolutionManagement { repositories { mavenLocal() mavenCentral() - - gradlePluginPortal() // so that external plugins can be resolved in dependencies section + gradlePluginPortal() } - // kinda hacky way to make a catalog from our own catalog, but it works! - // Link: https://github.com/gradle/gradle/issues/15383#issuecomment-779893192 versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) @@ -18,8 +14,11 @@ dependencyResolutionManagement { } } -plugins { - +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } } include("build-conventions") diff --git a/build.gradle.kts b/build.gradle.kts index dc92a3a..4544999 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,137 +1,191 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import com.adarshr.gradle.testlogger.theme.ThemeType +import io.gitlab.arturbosch.detekt.Detekt import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent -import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED +import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED +import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION as KOTLIN_VERSION -@Suppress("DSL_SCOPE_VIOLATION") plugins { - alias(libs.plugins.github.release) alias(libs.plugins.kotlin.jvm) - alias(libs.plugins.semver) + + alias(libs.plugins.detekt) + alias(libs.plugins.spotless) + alias(libs.plugins.best.practices) + alias(libs.plugins.github.release) alias(libs.plugins.dependency.analysis) + alias(libs.plugins.binary.compatibility.validator) - id("local.publishing") // maven and gradle publishing info - build-logic/publishing - id("local.analysis-conventions") - id("local.licenser") + alias(libs.plugins.test.logger) + alias(libs.plugins.gradle.testkit) + + id("local.publishing") + + idea } -semver { - tagPrefix("v") - initialVersion("0.0.1") - findProperty("semver.overrideVersion")?.toString()?.let { overrideVersion(it) } - findProperty("semver.modifier")?.toString() - ?.let { versionModifier(buildVersionModifier(it)) } // this is only used for non user defined strategies, ie predefined Flow or Flat +group = "com.figure.gradle" +version = "2.0.0" +description = "Gradle Plugin for Automated Semantic Versioning" + +val testImplementation: Configuration by configurations.getting + +val functionalTestImplementation: Configuration by configurations.getting { + extendsFrom(testImplementation) } -configurations.all { - resolutionStrategy { - eachDependency { - if (requested.group == "org.jetbrains.kotlin") { - useVersion(libs.versions.kotlin.get()) - } - } +sourceSets { + named("functionalTest") { + compileClasspath += sourceSets.main.get().compileClasspath + sourceSets.main.get().output + runtimeClasspath += output + compileClasspath } } -repositories { - mavenCentral() +dependencies { + implementation(gradleKotlinDsl()) + + implementation(libs.jgit) + implementation(libs.kotlin.semver) + + testImplementation(gradleTestKit()) + testImplementation(libs.kotest.runner) + testImplementation(libs.kotest.datatest) + + functionalTestImplementation(libs.testkit.support) } -dependencies { - listOf( - gradleApi(), - gradleKotlinDsl(), - libs.eclipse.jgit.eclipseJgit, - ).forEach { - implementation(it) +tasks { + withType().configureEach { + compilerOptions { + freeCompilerArgs.addAll("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") + } } - // Leak semver library users of this plugin so that they can implement their own versionModifier strategy - // TODO: For v2, remove need for semver jar for consumers. They shouldn't need to know about this detail - listOf( - libs.swiftzer.semver, - ).forEach { - api(it) + withType().configureEach { + useJUnitPlatform() + // To be able to use withEnvironment: https://github.com/kotest/kotest/issues/2849 + jvmArgs("--add-opens=java.base/java.util=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED") + testLogging { + showStandardStreams = false + showCauses = true + showStackTraces = true + events = setOf(SKIPPED, FAILED, STANDARD_ERROR) + exceptionFormat = TestExceptionFormat.FULL + } } - listOf( - gradleTestKit(), - libs.bundles.kotest, - ).forEach { - testImplementation(it) + wrapper { + distributionType = Wrapper.DistributionType.ALL } -} -// Enforce Kotlin version coherence -configurations.all { - resolutionStrategy.eachDependency { - if (requested.group == "org.jetbrains.kotlin" && requested.name.startsWith("kotlin")) { - useVersion(KOTLIN_VERSION) - because("All Kotlin modules should use the same version, and compiler uses $KOTLIN_VERSION") + check { + dependsOn("detekt") + } + + withType().configureEach { + reports { + txt.required.set(true) + html.required.set(true) + xml.required.set(false) + sarif.required.set(false) } } -} -kotlin { - jvmToolchain(11) + register("fmt") { + group = "verification" + description = "Format all code using configured formatters. Runs 'spotlessApply'" + dependsOn("spotlessApply") + } + + register("lint") { + group = "verification" + description = "Check all code using configured linters. Runs 'spotlessCheck'" + dependsOn("spotlessCheck") + } } -tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) - freeCompilerArgs.addAll( - "-version", - "-Xjsr305=strict", - "-opt-in=kotlin.RequiresOptIn", - ) - verbose.set(true) +idea { + module { + // Marks the functionTest as a test source set + testSources.from(sourceSets.functionalTest.get().allSource.srcDirs) } } +kotlin { + jvmToolchain(17) +} + java { withSourcesJar() withJavadocJar() } -tasks.withType().configureEach { - useJUnitPlatform() - testLogging { - showStandardStreams = true - showCauses = true - showStackTraces = true - events = TestLogEvent.values().toSet() - exceptionFormat = TestExceptionFormat.FULL - } +detekt { + buildUponDefaultConfig = true + allRules = false + config.from(files("detekt.yml")) } -// project version, also used for publishing -version = semver.version +spotless { + format("misc") { + target("*.md", "*.gitignore", "*.yml", "*.yaml", "*.toml", "*.properties") + trimTrailingWhitespace() + endWithNewline() + } -val githubTokenValue: String = findProperty("githubToken")?.toString() ?: System.getenv("GITHUB_TOKEN") + kotlin { + target("src/**/*.kt") + ktlint() + trimTrailingWhitespace() + endWithNewline() + licenseHeaderFile(rootProject.file("spotless/license.kt")) + } -githubRelease { - apiEndpoint = "https://api.github.com" // should only change for GitHub enterprise users - body = "" - draft = false - dryRun = false // by default false; you can use this to see what actions would be taken without making a release - generateReleaseNotes = true - overwrite = false - owner = "FigureTechnologies" - prerelease = false - repo = "gradle-semver-plugin" // Should automatically pick up your project name, overwrite this if you need something else - targetCommitish = System.getenv("TARGET_COMMIT_ISH")?.takeIf { it.isNotBlank() } ?: "main" + kotlinGradle { + target("*.kts", "src/**/*.kts") + ktlint() + trimTrailingWhitespace() + endWithNewline() + licenseHeaderFile( + rootProject.file("spotless/license.kt"), + "(import|plugins|buildscript|dependencies|pluginManagement|dependencyResolutionManagement)", + ) + } +} - // This is your personal access token with Repo permissions - // You get this from your user settings > developer settings > Personal Access Tokens - token(githubTokenValue) +testlogger { + theme = ThemeType.STANDARD + showCauses = true + slowThreshold = 1000 + showSummary = true + showStandardStreams = false } -tasks.wrapper { - distributionType = Wrapper.DistributionType.ALL +apiValidation { + ignoredPackages += + listOf( + // Internal package is not part of the public API + "com.figure.gradle.semver.internal", + ) } -logger.lifecycle("JDK toolchain version: ${java.toolchain.languageVersion.get()}") -logger.lifecycle( - "Kotlin version: ${extensions.findByType()?.coreLibrariesVersion}", -) +githubRelease { + generateReleaseNotes = true + owner = "FigureTechnologies" + providers.environmentVariable("GITHUB_TOKEN").map { token(it) } +} diff --git a/detekt-baseline.xml b/detekt-baseline.xml new file mode 100644 index 0000000..17c5ecf --- /dev/null +++ b/detekt-baseline.xml @@ -0,0 +1,8 @@ + + + + + NestedBlockDepth:GradleProjectListener.kt$GradleProjectListener$fun initRepository(config: RepositoryConfig, printLocalGitObjects: Boolean = true): GradleProjectListener + TooManyFunctions:LogExtensions.kt$com.figure.gradle.semver.internal.logging.LogExtensions.kt + + diff --git a/detekt.yml b/detekt.yml index 3a93d3d..fbf3587 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,12 +1,5 @@ style: - ReturnCount: - active: false - MagicNumber: + MaxLineLength: # defer to ktlint active: false ForbiddenComment: active: false - ThrowsCount: - active: true - excludeGuardClauses: true - MaxLineLength: - excludeCommentStatements: true diff --git a/docs/append-build-metadata.md b/docs/append-build-metadata.md new file mode 100644 index 0000000..aea15d8 --- /dev/null +++ b/docs/append-build-metadata.md @@ -0,0 +1,51 @@ +# Append Build Metadata + +Build metadata is a string of characters in the format: `+` + +To conditionally append build metadata to the next version, use the Gradle +property: + +**Via command line:** + +```shell +-Psemver.appendBuildMetadata= +``` + +**In any valid `gradle.properties`:** + +```properties +semver.appendBuildMetadata= +``` + +???+ note "Important" + If no value is provided, a default of `never` will be used. + +The following are the possible values: + +| Modifier | Description | +|-----------|--------------------------------------------------------------| +| `never` | Never adds the generated build metadata | +| `always` | Always adds the generated build metadata | +| `locally` | Only adds the generated build metadata when building locally | + +### Examples + +Latest tag: `v1.0.0` + +Current date and time: `01-23-2024 12:34` + +| Command | Next Version | +|--------------------------------------------------|---------------------------------------------| +| `./gradlew -Psemver.appendBuildMetadata=locally` | 1.0.1+202401231234 (when not running in CI) | +| `./gradlew -Psemver.appendBuildMetadata=always` | 1.0.1+202401231234 | +| `./gradlew -Psemver.appendBuildMetadata=never` | 1.0.1 | + +Latest tag: `v1.0.0-feat.1` (and still on this feature branch) + +Current date and time: `01-23-2024 12:34` + +| Command | Next Version | +|--------------------------------------------------|----------------------------------------------------| +| `./gradlew -Psemver.appendBuildMetadata=locally` | 1.0.1-feat.2+202401231234 (when not running in CI) | +| `./gradlew -Psemver.appendBuildMetadata=always` | 1.0.0-feat.2+202401231234 | +| `./gradlew -Psemver.appendBuildMetadata=never` | 1.0.0-feat.2 | diff --git a/docs/assets/package-white.svg b/docs/assets/package-white.svg new file mode 100644 index 0000000..cf86896 --- /dev/null +++ b/docs/assets/package-white.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/docs/branch-based-version-calculation.md b/docs/branch-based-version-calculation.md new file mode 100644 index 0000000..1d08a9b --- /dev/null +++ b/docs/branch-based-version-calculation.md @@ -0,0 +1,51 @@ +Branch-based version calculation provides a way to automatically generate unique versions based on the current branch. +This can be good for local testing and integration testing with other libraries +or services for a period of time prior to creating a stable release. + +???+ info "Important" + Every commit gets a new version! + +### Examples + +Branching from the main branch + +| Latest Tag | Current Branch | Branched From | Commits past main | Next version | +|------------|------------------------|---------------|-------------------|------------------------------| +| `v1.0.0` | `me/sc-123/my-feature` | `main` | 4 | 1.0.1-me-sc-123-my-feature.4 | +| `v1.0.0` | `my-feature` | `main` | 12 | 1.0.1-my-feature.12 | +| `v1.0.0` | `my-sub-feature` | `my-feature` | 16 | 1.0.1-my-sub-feature.16 | +| `v1.0.0` | `main` | - | 7 | 1.0.1 | + +Branching from the development branch + +| Latest Tag | Current Branch | Branched From | Commits past develop | Next version | +|--------------------|------------------------|---------------|----------------------|------------------------------| +| `v1.0.0` | `me/sc-123/my-feature` | `develop` | 4 | 1.0.1-me-sc-123-my-feature.4 | +| `v1.0.0` | `my-feature` | `develop` | 12 | 1.0.1-my-feature.12 | +| `v1.0.0` | `my-sub-feature` | `my-feature` | 16 | 1.0.1-my-sub-feature.16 | +| `v1.0.0-develop.1` | `develop` | - | 7 | 1.0.1-develop.8 | + +### Forcing a new version + +???+ tip + Need a new version but don't need to make any changes to your branch? + Just create an empty commit! + +```shell +git commit --allow-empty -m "Empty commit" +``` + +Alternatively, you can use the `semver.appendBuildMetadata` property to append build metadata to the version. + +**Via command line:** + +```shell +./gradlew -Psemver.appendBuildMetadata=(always|never|locally) +``` + +**In any valid `gradle.properties`:** + +```properties +semver.appendBuildMetadata=(always|never|locally) +``` + diff --git a/docs/for-major-version.md b/docs/for-major-version.md new file mode 100644 index 0000000..cd00092 --- /dev/null +++ b/docs/for-major-version.md @@ -0,0 +1,30 @@ +# For Major Version + +To target a historical major version line (useful for applying fixes to older +major versions), use the Gradle property: + +**Via command line:** + +```shell +-Psemver.forMajorVersion= +``` + +**In any valid `gradle.properties`:** + +```properties +semver.forMajorVersion= +``` + +### Examples + +Latest tag: `v2.0.0` +Latest v1 tag: `v1.5.9` + +???+ note "Important Note" + If no stage or modifier is provided, a default of `auto` used. + +| Command | Next Version | +|-------------------------------------------------------------------------------------|--------------| +| `./gradlew -Psemver.forMajorVersion=1` | 1.5.10 | +| `./gradlew -Psemver.forMajorVersion=1` -Psemver.modifier=minor | 1.6.0 | +| `./gradlew -Psemver.forMajorVersion=1` -Psemver.modifier=minor -Psemver.modifier=rc | 1.6.0-rc.1 | diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d986c6e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,77 @@ +# Semver Gradle Plugin + +This Semver Gradle plugin provides a simple approach to +adding semantic versioning to your gradle project using git +history regardless of git strategies. + +At a glance, this plugin provides the following features: + +- Support for stages (`rc`, `beta`, `stable`, `snapshot`, etc. - see below) +- Support for modifiers (`auto`, `patch`, `minor`, `major`) +- Support for branch-based version calculations +- Support for overriding the version +- Support for setting an alternate initial version +- Support for specifying alternate main and development branch names +- Support for appending build metadata (format: `+`) +- Build support when + - No git repository is present + - No git tags are present + - No remote branch is present + - Merging, rebasing, cherry-picking, bisecting, reverting, or in a detached + head state + +## Installation + +The following can be added to any of the following: + +- `settings.gradle.kts` (recommended) + - This will automatically apply the version to all projects +- `build.gradle.kts` (root project) + - This will only automatically apply the version to the root project +- `build.gradle.kts` (subproject) + - This will only automatically apply the version to the subproject + +If the semantic version is targeting the entire project, it's recommended to add +this to the `settings.gradle.kts` file. + +```kotlin +plugins { + id("com.figure.gradle.semver") version "" +} +``` + +## Configuration + +???+ info "Important!" + The most minimal configuration is to not provide any configuration at all. This + will use the default settings and will generate a version based on the git + history. + + However, configurations exist to allow for more control over the versioning + calculation process. + +```kotlin +// For older versions of gradle, you may need to import the configuration method +import com.figure.gradle.semver.semver + +// This is purely for example purposes +semver { + // Default: `settings.settingsDir` + rootProjectDir = settingsDir.parent + + // Default: `0.0.0` (first build will generate `0.0.1`) + initialVersion = "1.0.0" + + // No "default", but the plugin will search in order for: + // `main`, `master + mainBranch = "trunk" + + // No "default", but the plugin will search in order for: + // `develop`, `devel`, `dev` + developmentBranch = "development" + + // Default: `never` + // Options: `never`, `always`, `locally` + appendBuildMetadata = "locally" +} +``` diff --git a/docs/modifiers-with-stages.md b/docs/modifiers-with-stages.md new file mode 100644 index 0000000..9ec1734 --- /dev/null +++ b/docs/modifiers-with-stages.md @@ -0,0 +1,138 @@ +# Modifiers with Stages + +To alter the next version incrementation and stage, use the Gradle +properties together: + +**Via command line:** + +```shell +-Psemver.modifier= -Psemver.stage= +``` + +**In any valid `gradle.properties`:** + +```properties +semver.modifier= +semver.stage= +``` + +Latest tag: `v1.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=major` | 2.0.0-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=major` | 2.0.0-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=major` | 2.0.0-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=major` | 2.0.0-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=major` | 2.0.0-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=major` | 2.0.0-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=major` | 2.0.0-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=major` | 2.0.0-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=major` | 2.0.0 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=major` | 2.0.0-rc.1 | + +Latest tag: `v1.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=minor` | 1.1.0-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=minor` | 1.1.0-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=minor` | 1.1.0-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=minor` | 1.1.0-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=minor` | 1.1.0-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=minor` | 1.1.0-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=minor` | 1.1.0-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=minor` | 1.1.0-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=minor` | 1.1.0 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=minor` | 1.1.0-rc.1 | + +Latest tag: `v1.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=patch` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=patch` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=patch` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=patch` | 1.0.1-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=patch` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=patch` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=patch` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=patch` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=patch` | 1.0.1 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=patch` | 1.0.1-rc.1 | + +Latest tag: `v1.0.0-rc.1` + +| Command | Next Version | +|------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=auto` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=auto` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=auto` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=auto` | 1.0.0-rc.2 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=auto` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=auto` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=auto` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=auto` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=auto` | 1.0.1 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=auto` | 1.0.0-rc.2 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=major` | 2.0.0-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=major` | 2.0.0-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=major` | 2.0.0-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=major` | 2.0.0-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=major` | 2.0.0-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=major` | 2.0.0-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=major` | 2.0.0-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=major` | 2.0.0-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=major` | 2.0.0 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=major` | 2.0.0 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=minor` | 1.1.0-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=minor` | 1.1.0-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=minor` | 1.1.0-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=minor` | 1.1.0-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=minor` | 1.1.0-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=minor` | 1.1.0-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=minor` | 1.1.0-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=minor` | 1.1.0-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=minor` | 1.1.0 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=minor` | 1.1.0 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|-------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=patch` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=patch` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=patch` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=patch` | 1.0.1-rc.1 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=patch` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=patch` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=patch` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=patch` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=patch` | 1.0.1 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=patch` | 1.0.1 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|------------------------------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev -Psemver.modifier=auto` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha -Psemver.modifier=auto` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta -Psemver.modifier=auto` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc -Psemver.modifier=auto` | 1.0.0-rc.2 | +| `./gradlew -Psemver.stage=snapshot -Psemver.modifier=auto` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final -Psemver.modifier=auto` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga -Psemver.modifier=auto` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release -Psemver.modifier=auto` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable -Psemver.modifier=auto` | 1.0.1 | +| `./gradlew -Psemver.stage=auto -Psemver.modifier=auto` | 1.0.1 | + diff --git a/docs/modifiers.md b/docs/modifiers.md new file mode 100644 index 0000000..842250e --- /dev/null +++ b/docs/modifiers.md @@ -0,0 +1,70 @@ +# Modifiers + +To alter the next version incrementation, use the Gradle +property: + +**Via command line:** +```shell +-Psemver.modifier= +``` + +**In any valid `gradle.properties`:** +```properties +semver.modifier= +``` + +???+ note + If no modifier is provided, a default of `auto` will be used. + +The following are the possible values: + +| Modifier | Description | +|----------|--------------------------------------------------------------------------------------------------| +| `major` | Increments the major version number | +| `minor` | Increments the minor version number | +| `patch` | Increments the patch version number | +| `auto` | Increments the patch or the pre-release number if the previous tag was a stage-based pre-release | + +### Examples + +???+ note "Important Note" + Since no stage is provided in these examples, the default stage of `auto` + used. + + For how to use with stages, consult the [Modifiers with Stages](modifiers-with-stages.md) documentation. + +Latest tag: `v1.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------|--------------| +| `./gradlew -Psemver.modifier=major` | v2.0.0 | +| `./gradlew -Psemver.modifier=minor` | v1.1.0 | +| `./gradlew -Psemver.modifier=patch` | v1.0.1 | +| `./gradlew -Psemver.modifier=auto` | v1.0.0-rc.2 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|-------------------------------------|--------------| +| `./gradlew -Psemver.modifier=major` | v2.0.0 | +| `./gradlew -Psemver.modifier=minor` | v1.1.0 | +| `./gradlew -Psemver.modifier=patch` | v1.0.1 | +| `./gradlew -Psemver.modifier=auto` | v1.0.1 | + +Latest tags (sorted by latest first) and on main branch: + +- `v1.0.1-my-feature.1` +- `v1.0.0` + +???+ info "Important" + The latest tag is `v1.0.1-my-feature.1`, however, this is a special + pre-release type that does not affect the calculation of the + next version when on a main branch given a modifier. + +| Command | Next Version | +|-------------------------------------|--------------| +| `./gradlew -Psemver.modifier=major` | v2.0.0 | +| `./gradlew -Psemver.modifier=minor` | v1.1.0 | +| `./gradlew -Psemver.modifier=patch` | v1.0.1 | +| `./gradlew -Psemver.modifier=auto` | v1.0.1 | + diff --git a/docs/override-version.md b/docs/override-version.md new file mode 100644 index 0000000..ae7c975 --- /dev/null +++ b/docs/override-version.md @@ -0,0 +1,30 @@ +# Override Version + +To specify the exact next version, use the Gradle property: + +**Via command line:** + +```shell +-Psemver.overrideVersion= +``` + +**In any valid `gradle.properties`:** + +```properties +semver.overrideVersion= +``` + +### Examples + +Latest tag: `v2.0.0` + +| Command | Next Version | +|--------------------------------------------|--------------| +| `./gradlew -Psemver.overrideVersion=1.5.9` | v1.5.9 | + + +Latest tag: `v2.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------------------------------|-------------------------| +| `./gradlew -Psemver.overrideVersion=1.5.9-my-test-hotfix.1` | v1.5.9-my-test-hotfix.1 | diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 0000000..868e648 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,41 @@ +# Quick Start + +This Gradle plugin calculates the next version based on the latest Git tag, +using optional parameters and properties. + +Basic usage: + +```shell +./gradlew build +``` + +With parameters: + +```shell +./gradlew build -Psemver.stage=rc -Psemver.modifier=minor +``` + +## Parameters and Properties + +The plugin uses several parameters and properties to calculate the next version. +Default values and valid inputs vary. For detailed information on each parameter +and property, including usage examples and valid values, please refer to the +following documentation pages: + +1. [semver.stage](stages.md) +2. [semver.modifier](modifiers.md) +3. [semver.overrideVersion](override-version.md) +4. [semver.forMajorVersion](for-major-version.md) +5. [semver.tagPrefix](tag-prefix.md) +6. [semver.appendBuildMetadata](append-build-metadata.md) + +???+ info "Important!" + Each of these can be set as: + + - Gradle parameters (`-P` on the command-line) + - in any valid `gradle.properties` files, in search order: + - in the Gradle user home directory ( + typically `~/.gradle/gradle.properties`) + - in the Gradle home directory + - in the project's directory where the plugin is applied + diff --git a/docs/semver.md b/docs/semver.md deleted file mode 100644 index 7ddebe2..0000000 --- a/docs/semver.md +++ /dev/null @@ -1,71 +0,0 @@ -This file is a copy/paste from the old `platform-portal` repo for posterity. It should eventually be reviewed/ - ---- - -# Semantic Versioning - -Official specification and description: https://semver.org/ - -## Using the Semver Gradle plugin - -See full documentation here: https://github.com/FigureTechnologies/gradle-semver-plugin - -### TL;DR - -The version (and tag name) is calculated at runtime from a combination of current branch & target branch (version). If a `develop` branch is present `Flow` mode will be enabled, if it's absent, `Flat` mode will be used. See the official documentation for the default configurations for each (branch regexes). Here is a [minimal and typical example of usage](https://github.com/FigureTechnologies/libs-exposed-tools/blob/main/build-logic/build-conventions/src/main/kotlin/figure.build-conventions.gradle.kts#L4-L9). -It is important to use the expected branch name, so that a branch regex in the configuration will be matched. - -**Note** the preset configurations for `Flow` and `Flat` both assumes `main` branch, if you're still using `master`, see this [custom configuration as example (Flow mode)](https://github.com/FigureTechnologies/service-risk/blob/360b5a44e98c4b470896160dd19da66e8267e674/build-logic/build-conventions/src/main/kotlin/figure.build-conventions.gradle.kts). This example also adds support for branch names typically used with ShortCut, eg: -* `"""^.+/sc-\d+/.+""".toRegex()` -* `"""^.+/\d+/.+"""` -* `"""^.+/no-ticket/.+"""` - -More information on the version calculation is available by enabling info logging, eg running `./gradlew cv -i`. - -## Limiting dependencies to release versions - -A reasonable approach would be to use Gradle's attribute metadata, however it appears to be [broken](https://github.com/gradle/gradle/issues/20016). - -Another, less desirable approach would be to use a ComponentSelection rule, here's one such possibility in the build convention plugin: - -```kotlin -val InvalidQualifiers = setOf("alpha", "beta", "rc", "nightly") -val OnlyReleaseArtifacts = setOf("figure-retry", "stream-data") -val WhiteListedMavenGroups = setOf("com.figure", "tech.figure", "io.provenance") - -configurations.all { - resolutionStrategy { - componentSelection { - all { - when { - OnlyReleaseArtifacts.any { candidate.moduleIdentifier.name.startsWith(it) } && !safeVersion(candidate.version)?.preRelease.isNullOrEmpty() -> { - reject("rejecting prerelease version for OnlyReleaseArtifact[$candidate]") - } - WhiteListedMavenGroups.none { candidate.group.startsWith(it) } && InvalidQualifiers.any { candidate.version.contains(it) } -> { - reject("invalid qualifier versions for $candidate") - } - } - } - } - } -} - -fun safeVersion(version: String): SemVer? { - return try { - SemVer.parse(version) - } catch (e: Exception) { - println("failed to parse $version") - null - } -} -``` - -The `OnlyReleaseArtifacts` should take precedence over any white listed groups to enforce release only semantic versions. For example, in this case, any artifact which artifactID starts with `figure-retry` and has a pre-release version will be rejected until a version without a pre-release label is found. - -Make sure that https://github.com/swiftzer/semver is on the classpath for the buildscript, eg in FSB adding it to `build-logic/build-conventions/build.gradles.kts`: - -```kotlin -dependencies { - implementation(tpLibs.swiftzer.semver) -} -``` diff --git a/docs/stages.md b/docs/stages.md new file mode 100644 index 0000000..774e7d8 --- /dev/null +++ b/docs/stages.md @@ -0,0 +1,68 @@ +To alter the next version stage, use the Gradle property: + +**Via command line:** +```shell +-Psemver.stage= +``` + +**In any valid `gradle.properties`:** +```properties +semver.stage= +``` + +???+ note + If no stage is provided, a default of `auto` will be used. + +The following are possible values: + +| Stage | Pre-release Label | Example Tag | Description | +|------------|---------------------------|--------------------|----------------------------| +| `dev` | dev | `v1.0.0-dev.1` | Development stage | +| `alpha` | alpha | `v1.0.0-alpha.1` | Alpha stage | +| `beta` | beta | `v1.0.0-beta.1` | Beta stage | +| `rc` | rc | `v1.0.0-rc.1` | Release Candidate stage | +| `snapshot` | SNAPSHOT | `v1.0.0-SNAPSHOT` | Snapshot stage | +| `final` | final | `v1.0.0-final.1` | Final stage | +| `ga` | ga | `v1.0.0-ga.1` | General Availability stage | +| `release` | release | `v1.0.0-release.1` | Release stage | +| `stable` | (none) | `v1.0.0` | Stable stage | +| `auto` | (depends on previous tag) | - | Based on previous tag | + +### Examples + +???+ note "Important Note" + Since no modifier is provided in these examples, the default modifier + of `auto` used. + + For how to use with modifiers, consult the [Modifiers with Stages](modifiers-with-stages.md) documentation. + +Latest tags: `v1.0.0-rc.1` + +| Command | Next Version | +|-------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc` | 1.0.0-rc.2 | +| `./gradlew -Psemver.stage=snapshot` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable` | 1.0.0 | +| `./gradlew -Psemver.stage=auto` | 1.0.0-rc.2 | + +Latest tag: `v1.0.0` + +| Command | Next Version | +|-------------------------------------|-----------------| +| `./gradlew -Psemver.stage=dev` | 1.0.1-dev.1 | +| `./gradlew -Psemver.stage=alpha` | 1.0.1-alpha.1 | +| `./gradlew -Psemver.stage=beta` | 1.0.1-beta.1 | +| `./gradlew -Psemver.stage=rc` | 1.0.1-rc.1 | +| `./gradlew -Psemver.stage=snapshot` | 1.0.1-SNAPSHOT | +| `./gradlew -Psemver.stage=final` | 1.0.1-final.1 | +| `./gradlew -Psemver.stage=ga` | 1.0.1-ga.1 | +| `./gradlew -Psemver.stage=release` | 1.0.1-release.1 | +| `./gradlew -Psemver.stage=stable` | 1.0.1 | +| `./gradlew -Psemver.stage=auto` | 1.0.1 | + diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..51665bf --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,34 @@ +:root { + --md-primary-fg-color: #5A56EC; + --md-accent-fg-color: #5A56EC; +} + +th:not(:last-child), td:not(:last-child) { + border: 1px solid var(--md-typeset-table-color); + border-spacing: 0; + border-bottom: none; + border-left: none; + border-top: none; +} + +.md-typeset__table th, .md-typeset__table td { + word-wrap: break-word; + width: auto; /* Ensure columns auto-size based on content */ +} + +[data-md-color-scheme="slate"] .md-typeset__table tr:nth-child(2n) { + background-color: var(--md-typeset-table-color--light); +} + +[data-md-color-scheme="default"] .md-typeset__table tr:nth-child(2n) { + background-color: var(--md-typeset-table-color--light); +} + +/* Make sure tables are always 100% width */ +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/docs/tag-prefix.md b/docs/tag-prefix.md new file mode 100644 index 0000000..125f4b9 --- /dev/null +++ b/docs/tag-prefix.md @@ -0,0 +1,27 @@ +# Tag Prefix + +To alter the next version tag prefix, use the Gradle property: + +**Via command line:** + +```shell +-Psemver.tagPrefix= +``` + +**In any valid `gradle.properties`:** + +```properties +semver.tagPrefix= +``` + +???+ note + If no tag prefix is provided, a default of `v` will be used. + +### Examples + +Latest tag: `v1.0.0` + +| Command | Next Version | +|---------------------------------------------|-------------------| +| `./gradlew -Psemver.tagPrefix=my-prefix` | my-prefix1.0.1 | +| `./gradlew -Psemver.tagPrefix=support-lib-` | support-lib-1.1.0 | diff --git a/gradle.properties b/gradle.properties index 3b17ffa..559ca06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,4 @@ -org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -kotlin.code.style=official -semver.currentBranch.scope=patch -org.gradle.caching=true +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=512m org.gradle.parallel=true - - +org.gradle.caching=true +org.gradle.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ecab00b..59905f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,49 +1,42 @@ [versions] -kotlin = "1.9.23" - -# Library versions -detekt = "1.23.6" -gradle-plugin-publish = "1.2.1" -jgit = "6.5.0.202303070854-r" -kotest = "5.8.1" -ktlint-gradle = "12.1.0" -swiftzer-semver = "1.3.0" - -# Plugin versions -dependency-analysis = "1.31.0" -github-release = "2.5.2" -gradle-semver-plugin = "1.9.1" - -licenser = "0.6.1" +kotlin = "1.9.24" # Goal is to track whatever the latest stable version that is supported by Gradle -[libraries] +jgit = "7.0.0.202409031743-r" +kotest = "5.9.1" +kotlin-semver = "2.0.0" -# Libraries -eclipse-jgit-eclipseJgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgit" } -swiftzer-semver = { module = "net.swiftzer.semver:semver", version.ref = "swiftzer-semver" } +best-practices = "0.10" +binary-compatibility-validator = "0.16.3" +dependency-analysis = "2.0.2" +detekt = "1.23.7" +github-release = "2.5.2" +gradle-testkit = "0.10" +publish-plugin = "1.3.0" +spotless = "6.25.0" +test-logger = "4.0.0" +testkit-support = "0.16" -# Plugins needed as libraries -gradle-plugin-publish = { module = "com.gradle.plugin-publish:com.gradle.plugin-publish.gradle.plugin", version.ref = "gradle-plugin-publish" } -detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } -ktlint = { module = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin", version.ref = "ktlint-gradle" } -licenser = { module = "org.cadixdev.licenser:org.cadixdev.licenser.gradle.plugin", version.ref = "licenser" } +[libraries] -# Test Libraries -kotest-assertions-core-jvm = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest" } -kotest-framework-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" } -kotest-junit5-jvm = { module = "io.kotest:kotest-runner-junit5-jvm", version.ref = "kotest" } +jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgit" } +kotlin-semver = { module = "io.github.z4kn4fein:semver", version.ref = "kotlin-semver" } -[bundles] +kotest-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" } +kotest-runner = { module = "io.kotest:kotest-runner-junit5-jvm", version.ref = "kotest" } +testkit-support = { module = "com.autonomousapps:gradle-testkit-support", version.ref = "testkit-support" } -kotest = ["kotest-junit5-jvm", "kotest-assertions-core-jvm", "kotest-framework-datatest"] +gradle-plugin-publish = { module = "com.gradle.plugin-publish:com.gradle.plugin-publish.gradle.plugin", version.ref = "publish-plugin" } [plugins] +best-practices = { id = "com.autonomousapps.plugin-best-practices-plugin", version.ref = "best-practices" } +binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } dependency-analysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependency-analysis" } +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } github-release = { id = "com.github.breadmoirai.github-release", version.ref = "github-release" } -gradle-plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" } +gradle-testkit = { id = "com.autonomousapps.testkit", version.ref = "gradle-testkit" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -semver = { id = "com.figure.gradle.semver-plugin", version.ref = "gradle-semver-plugin" } - - +publish-plugin = { id = "com.gradle.plugin-publish", version.ref = "publish-plugin" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +test-logger = { id = "com.adarshr.test-logger", version.ref = "test-logger" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e7646de..1ed247e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..e2ad399 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,79 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json +site_name: 'Semver Gradle Plugin' +site_description: 'Gradle plugin to automatically calculate the next semantic version based on the git history.' +site_author: 'Tyler Crawford' +remote_branch: gh-pages + +repo_name: 'gradle-semver-plugin' +repo_url: 'https://github.com/FigureTechnologies/gradle-semver-plugin' + +copyright: 'Copyright © 2024 Tyler Crawford' + +theme: + name: material + language: 'en' + favicon: 'assets/package-white.svg' + logo: 'assets/package-white.svg' + font: + code: 'Robot Mono' + palette: + - media: '(prefers-color-scheme: light)' + scheme: default + primary: 'custom' + accent: 'custom' + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: 'custom' + accent: 'custom' + toggle: + icon: material/brightness-4 + name: Switch to light mode + +plugins: + - search + +markdown_extensions: + # Admonitions (aka callout blocks) + - admonition + - pymdownx.details + - pymdownx.superfences + + - smarty + - codehilite: + guess_lang: false + - footnotes + - meta + - toc: + permalink: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.inlinehilite + - pymdownx.magiclink + - pymdownx.smartsymbols + - pymdownx.emoji + - tables + +extra_css: + - stylesheets/extra.css + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/FigureTechnologies + +nav: + - "Getting Started": 'index.md' + - Usage: + - quick-start.md + - modifiers.md + - stages.md + - modifiers-with-stages.md + - append-build-metadata.md + - for-major-version.md + - override-version.md + - tag-prefix.md + - "Branch-based Version Calculation": 'branch-based-version-calculation.md' diff --git a/service.datadog.yaml b/service.datadog.yaml index e31d71d..2558278 100644 --- a/service.datadog.yaml +++ b/service.datadog.yaml @@ -11,4 +11,4 @@ repos: docs: - name: GitHub Team provider: github - url: https://github.com/orgs/FigureTechnologies/teams/backend-platform-dev-experience/members + url: https://github.com/orgs/FigureTechnologies/teams/backend-core-tech/members diff --git a/settings.gradle.kts b/settings.gradle.kts index 648dd52..ec7f4c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,36 +1,45 @@ -import org.gradle.api.internal.FeaturePreviews.Feature.STABLE_CONFIGURATION_CACHE -import org.gradle.api.internal.FeaturePreviews.Feature.TYPESAFE_PROJECT_ACCESSORS - -rootProject.name = "semver-plugin" - -enableFeaturePreview(STABLE_CONFIGURATION_CACHE.name) -enableFeaturePreview(TYPESAFE_PROJECT_ACCESSORS.name) - -dependencyResolutionManagement { +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +pluginManagement { repositories { mavenLocal() mavenCentral() + gradlePluginPortal() } + + includeBuild("build-logic") } -pluginManagement { +dependencyResolutionManagement { repositories { mavenLocal() - gradlePluginPortal() + mavenCentral() } - - includeBuild("build-logic") } plugins { - `gradle-enterprise` + id("com.gradle.develocity") version "3.18.1" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } -gradleEnterprise { +develocity { buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" - publishAlwaysIf(System.getenv("GITHUB_ACTIONS") == "true") - publishOnFailure() + termsOfUseUrl = "https://gradle.com/terms-of-service" + termsOfUseAgree = "yes" + val isCi = providers.environmentVariable("GITHUB_ACTIONS") + publishing.onlyIf { isCi.isPresent || it.buildResult.failures.isNotEmpty() } } } diff --git a/spotless/license.kt b/spotless/license.kt new file mode 100644 index 0000000..3642696 --- /dev/null +++ b/spotless/license.kt @@ -0,0 +1,15 @@ +/* + * Copyright (C) $YEAR Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitActions.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitActions.kt new file mode 100644 index 0000000..a105b07 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitActions.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.git + +import com.figure.gradle.semver.internal.command.KGit +import com.figure.gradle.semver.projects.AbstractProject +import com.figure.gradle.semver.util.resolveResource +import org.eclipse.jgit.lib.Constants +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import java.io.BufferedReader +import java.io.InputStreamReader + +private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) + +sealed interface Action { + fun execute(git: KGit) +} + +sealed interface RemovalAction : Action + +data class CheckoutAction( + val branch: String, +) : Action { + override fun execute(git: KGit) { + if (git.branch.headRef?.objectId?.name == null) { + git.commit("Initial commit", allowEmptyCommit = true) + } + + if (git.branches.exists(branch)) { + git.checkout(branch) + } else { + git.checkout(branch, createBranch = true) + } + } +} + +data class CommitAction( + val message: String = "Empty Commit", + val tag: String? = null, +) : Action { + override fun execute(git: KGit) { + git.commit(message, true) + + if (!tag.isNullOrBlank()) { + git.tag("v$tag") + } + } +} + +data class RunScriptAction( + val script: Script, + val project: AbstractProject, + val arguments: List, +) : Action { + override fun execute(git: KGit) { + val scriptFile = resolveResource("scripts/${script.scriptFileName}") + val projectPath = project.gradleProject.rootDir.absolutePath + + val processBuilder = ProcessBuilder() + processBuilder.command("bash", scriptFile.absolutePath, projectPath, *arguments.toTypedArray()) + processBuilder.redirectErrorStream(true) + val process = processBuilder.start() + + log.lifecycle("\nScript output:\n") + val reader = BufferedReader(InputStreamReader(process.inputStream)) + reader.useLines { lines -> + lines.forEach { log.lifecycle(it) } + } + log.lifecycle("\nEnd script output\n") + + process.waitFor() + } +} + +data class RemoveLocalBranchAction( + val branch: String, +) : RemovalAction { + override fun execute(git: KGit) { + git.branch.delete("${Constants.R_HEADS}$branch") + } +} + +data class RemoveRemoteBranchAction( + val branch: String, +) : RemovalAction { + override fun execute(git: KGit) { + git.branch.delete("${Constants.R_REMOTES}${Constants.DEFAULT_REMOTE_NAME}/$branch") + } +} + +class GitActionsConfig( + private val project: AbstractProject, +) { + val actions = mutableListOf() + + fun actions(config: Actions.() -> Unit) { + val actionObject = Actions(project) + actionObject.config() + actions.addAll(actionObject.actionsToRun) + } +} + +class Actions( + private val project: AbstractProject, +) { + internal val actionsToRun = mutableListOf() + + fun checkout(branch: String) { + val checkoutAction = CheckoutAction(branch) + actionsToRun.add(checkoutAction) + } + + fun commit( + message: String = "Empty Commit", + tag: String = "", + ) { + val commitAction = CommitAction(message, tag) + actionsToRun.add(commitAction) + } + + fun runScript( + script: Script, + vararg arguments: String, + ) { + val runScriptAction = RunScriptAction(script, project, arguments.toList()) + actionsToRun.add(runScriptAction) + } + + fun removeLocalBranch(branch: String) { + val removeLocalBranchAction = RemoveLocalBranchAction(branch) + actionsToRun.add(removeLocalBranchAction) + } + + fun removeRemoteBranch(branch: String) { + val removeRemoteBranchAction = RemoveRemoteBranchAction(branch) + actionsToRun.add(removeRemoteBranchAction) + } +} + +enum class Script( + val scriptFileName: String, +) { + CREATE_BISECTING_STATE("create_bisecting_state.sh"), + CREATE_CHERRY_PICKING_STATE("create_cherry_picking_state.sh"), + CREATE_DETACHED_HEAD_STATE("create_detached_head_state.sh"), + CREATE_MERGING_STATE("create_merging_state.sh"), + CREATE_REBASING_STATE("create_rebasing_state.sh"), + CREATE_REVERTING_STATE("create_reverting_state.sh"), +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceBuilder.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceBuilder.kt new file mode 100644 index 0000000..b4d43bb --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceBuilder.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.git + +import com.figure.gradle.semver.projects.AbstractProject + +fun gitInstance( + project: AbstractProject, + block: GitInstance.Builder.() -> Unit, +): GitInstance = GitInstance.Builder(project).apply(block).build() + +data class GitInstance( + val project: AbstractProject, + val debugging: Boolean, + val initialBranch: String, + val actions: GitActionsConfig, +) { + class Builder( + private val project: AbstractProject, + ) { + var debugging: Boolean = false + var initialBranch: String = "main" + var actions: GitActionsConfig = GitActionsConfig(project) + + fun actions(config: Actions.() -> Unit): GitActionsConfig { + actions.actions(config) + return actions + } + + fun build() = + GitInstance( + project = project, + debugging = debugging, + initialBranch = initialBranch, + actions = actions, + ) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceWriter.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceWriter.kt new file mode 100644 index 0000000..9a53598 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/git/GitInstanceWriter.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.git + +import com.figure.gradle.semver.internal.command.KGit + +class GitInstanceWriter( + private val localGit: KGit, + private val gitActionsConfig: GitActionsConfig, +) { + fun write(printGitObjects: Boolean) { + gitActionsConfig.actions + .filterNot { it is RemovalAction } + .forEach { action -> action.execute(localGit) } + + localGit.push.all() + + // This will run any removal actions + gitActionsConfig.actions + .filterIsInstance() + .forEach { action -> action.execute(localGit) } + + localGit.print.commits(printGitObjects) + localGit.print.refs(printGitObjects) + localGit.print.tags(printGitObjects) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCache.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCache.kt new file mode 100644 index 0000000..aa9a4f9 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCache.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.gradle + +import com.figure.gradle.semver.kit.render.Element +import com.figure.gradle.semver.kit.render.Scribe + +fun buildCache(fn: BuildCache.Builder.() -> Unit): BuildCache { + val builder = BuildCache.Builder() + builder.fn() + return builder.build() +} + +class BuildCache( + private val local: BuildCacheLocal? = null, +) : Element.Block { + override val name: String = "buildCache" + + override fun render(scribe: Scribe): String = + scribe.block(this) { s -> + local?.render(s) + } + + class Builder { + var local: BuildCacheLocal? = null + + fun build(): BuildCache = + BuildCache( + local = local, + ) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCacheLocal.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCacheLocal.kt new file mode 100644 index 0000000..6df9746 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/BuildCacheLocal.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.gradle + +import com.figure.gradle.semver.kit.render.Element +import com.figure.gradle.semver.kit.render.Scribe +import java.io.File + +fun local(fn: BuildCacheLocal.Builder.() -> Unit): BuildCacheLocal { + val builder = BuildCacheLocal.Builder() + builder.fn() + return builder.build() +} + +class BuildCacheLocal( + private val directory: File? = null, +) : Element.Block { + override val name: String = "local" + + override fun render(scribe: Scribe): String = + scribe.block(this) { s -> + directory?.let { + s.line { + s.append("directory = file(\"") + s.append(directory.absolutePath) + s.append("\")") + } + } + } + + class Builder { + var directory: File? = null + + fun build(): BuildCacheLocal = + BuildCacheLocal( + directory = directory, + ) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SemverConfiguration.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SemverConfiguration.kt new file mode 100644 index 0000000..c9612c5 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SemverConfiguration.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.gradle + +import com.figure.gradle.semver.kit.render.Element +import com.figure.gradle.semver.kit.render.Scribe +import java.io.File + +fun semver(fn: SemverConfiguration.Builder.() -> Unit): SemverConfiguration { + val builder = SemverConfiguration.Builder() + builder.fn() + return builder.build() +} + +class SemverConfiguration( + val rootProjectDir: File? = null, + val initialVersion: String?, + val mainBranch: String?, + val developmentBranch: String?, + val appendBuildMetadata: String?, +) : Element.Block { + override val name: String = "semver" + + override fun render(scribe: Scribe): String = + scribe.block(this) { s -> + rootProjectDir?.let { + s.line { s.append("rootProjectDir = \"$rootProjectDir\"") } + } + initialVersion?.let { + s.line { s.append("initialVersion = \"$initialVersion\"") } + } + mainBranch?.let { + s.line { s.append("mainBranch = \"$mainBranch\"") } + } + developmentBranch?.let { + s.line { s.append("developmentBranch = \"$developmentBranch\"") } + } + appendBuildMetadata?.let { + s.line { s.append("appendBuildMetadata = \"$appendBuildMetadata\"") } + } + } + + class Builder { + var rootProjectDir: File? = null + var initialVersion: String? = null + var mainBranch: String? = null + var developmentBranch: String? = null + var appendBuildMetadata: String? = null + + fun build(): SemverConfiguration = + SemverConfiguration( + rootProjectDir = rootProjectDir, + initialVersion = initialVersion, + mainBranch = mainBranch, + developmentBranch = developmentBranch, + appendBuildMetadata = appendBuildMetadata, + ) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SettingsConfiguration.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SettingsConfiguration.kt new file mode 100644 index 0000000..0df4058 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/SettingsConfiguration.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.gradle + +import com.figure.gradle.semver.kit.render.Element +import com.figure.gradle.semver.kit.render.Scribe + +fun settingsGradle(fn: SettingsConfiguration.Builder.() -> Unit): SettingsConfiguration { + val builder = SettingsConfiguration.Builder() + builder.fn() + return builder.build() +} + +class SettingsConfiguration( + private var buildCache: BuildCache? = null, + private var semver: SemverConfiguration? = null, +) : Element.Line { + override fun render(scribe: Scribe): String = + buildString { + semver?.let { sv -> + append(scribe.use { s -> sv.render(s) }) + appendLine() + } + + buildCache?.let { bc -> + append(scribe.use { s -> bc.render(s) }) + appendLine() + } + } + + class Builder { + var buildCache: BuildCache? = null + var semver: SemverConfiguration? = null + + fun build(): SettingsConfiguration = + SettingsConfiguration( + buildCache = buildCache, + semver = semver, + ) + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/runner.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/runner.kt new file mode 100644 index 0000000..1726efc --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/gradle/runner.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.gradle + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.util.GradleVersion +import java.io.File + +fun build( + gradleVersion: GradleVersion, + projectDir: File, + vararg args: String, +): BuildResult = runner(gradleVersion, projectDir, *args).build() + +fun buildAndFail( + gradleVersion: GradleVersion, + projectDir: File, + vararg args: String, +): BuildResult = runner(gradleVersion, projectDir, *args).buildAndFail() + +fun runWithoutExpectations( + gradleVersion: GradleVersion, + projectDir: File, + vararg args: String, +): BuildResult = runner(gradleVersion, projectDir, *args).run() + +fun runner( + gradleVersion: GradleVersion, + projectDir: File, + vararg args: String, +): GradleRunner = + GradleRunner.create().apply { + val arguments = + setOf( + *args, + "--parallel", + "--build-cache", + "--configuration-cache", + "--stacktrace", + "-Psemver.forTesting=true", + ).toList() + + forwardOutput() + withGradleVersion(gradleVersion.version) + withProjectDir(projectDir) + withArguments(arguments) + } diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Element.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Element.kt new file mode 100644 index 0000000..f9190b2 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Element.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.kit.render + +// TODO: Switch to official version if internal is removed from Scribe API +public sealed interface Element { + public fun render(scribe: Scribe): String + + public fun start(indent: Int): String = " ".repeat(indent) + + public interface Block : Element { + public val name: String + } + + public interface Line : Element +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Scribe.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Scribe.kt new file mode 100644 index 0000000..7ee855f --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/kit/render/Scribe.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.kit.render + +import com.autonomousapps.kit.GradleProject + +public class Scribe @JvmOverloads constructor( + /** Which Gradle DSL to use for rendering. */ + public val dslKind: GradleProject.DslKind = GradleProject.DslKind.KOTLIN, + /** Indent level when entering a block. */ + public val indent: Int = 2, +) : AutoCloseable { + private val buffer = StringBuilder() + + /** Starting indent for any block. */ + private var start: Int = 0 + + /** Enter a block, increase the indent. */ + private fun enter() { + start += indent + } + + /** Exit a block, decrease the indent. */ + private fun exit() { + start -= indent + } + + override fun close() { + buffer.clear() + start = 0 + } + + public fun block( + element: Element.Block, + block: (Scribe) -> Unit, + ): String { + // e.g., "plugins {" + indent() + buffer.append(element.name) + buffer.appendLine(" {") + + // increase the indent + enter() + + // write the block inside the {} + block(this) + + // decrease the indent + exit() + + // closing brace + indent() + buffer.appendLine("}") + + // return the string + return buffer.toString() + } + + public fun line(block: (Scribe) -> Unit): String { + indent() + block(this) + buffer.appendLine() + + return buffer.toString() + } + + public fun append(obj: Any?) { + buffer.append(obj.toString()) + } + + public fun appendLine() { + buffer.appendLine() + } + + private fun indent() { + buffer.append(" ".repeat(start)) + } + + public fun appendQuoted(obj: Any?) { + append(quote()) + append(obj.toString()) + append(quote()) + } + + private fun quote(): String = if (dslKind == GradleProject.DslKind.GROOVY) "'" else "\"" +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/GradleProjectsContainer.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/GradleProjectsContainer.kt new file mode 100644 index 0000000..1676d34 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/GradleProjectsContainer.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.kotest + +import com.figure.gradle.semver.projects.AbstractProject +import com.figure.gradle.semver.projects.GradleProjects +import io.kotest.core.extensions.MountableExtension +import io.kotest.core.listeners.AfterSpecListener +import io.kotest.core.listeners.AfterTestListener +import io.kotest.core.spec.Spec +import io.kotest.core.test.TestCase +import io.kotest.core.test.TestResult + +class GradleProjectsExtension( + private vararg val abstractProjects: AbstractProject, +) : MountableExtension, AfterSpecListener, AfterTestListener { + private lateinit var projects: GradleProjects + + override fun mount(configure: Unit.() -> Unit): GradleProjects { + return GradleProjects.gradleProjects(projects = abstractProjects).also { projects = it } + } + + override suspend fun afterSpec(spec: Spec) { + projects.close() + } + + override suspend fun afterAny( + testCase: TestCase, + result: TestResult, + ) { + projects.cleanAfterAny() + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/matchers.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/matchers.kt new file mode 100644 index 0000000..5c90f41 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/kotest/matchers.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.kotest + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldMatch + +infix fun Iterable.shouldOnlyHave(t: T): Iterable = this.map { it shouldBe t } + +infix fun Iterable.shouldOnlyContain(t: String): Iterable = this.map { it shouldContain t } + +infix fun Iterable.shouldOnlyMatch(t: Regex): Iterable = this.map { it shouldMatch t } diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/plugins/GradlePlugins.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/plugins/GradlePlugins.kt new file mode 100644 index 0000000..abff51e --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/plugins/GradlePlugins.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.plugins + +import com.autonomousapps.kit.AbstractGradleProject.Companion.PLUGIN_UNDER_TEST_VERSION +import com.autonomousapps.kit.gradle.Plugin + +object GradlePlugins { + val KOTLIN_VERSION: String = KotlinVersion.CURRENT.toString() + val semverPluginId = "com.figure.gradle.semver" + + val semverPlugin: Plugin = Plugin(semverPluginId, PLUGIN_UNDER_TEST_VERSION) + + val kotlinNoVersion: Plugin = Plugin("org.jetbrains.kotlin.jvm", null, true) + val kotlinNoApply: Plugin = Plugin("org.jetbrains.kotlin.jvm", KOTLIN_VERSION, false) +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/projects/AbstractProject.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/AbstractProject.kt new file mode 100644 index 0000000..ef176ff --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/AbstractProject.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.projects + +import com.autonomousapps.kit.AbstractGradleProject +import com.autonomousapps.kit.GradleProject +import com.figure.gradle.semver.Constants +import com.figure.gradle.semver.git.GitInstance +import com.figure.gradle.semver.git.GitInstanceWriter +import com.figure.gradle.semver.internal.command.InitializeRepo +import com.figure.gradle.semver.internal.command.KGit +import com.figure.gradle.semver.kit.render.Scribe +import java.io.File +import java.util.Properties +import kotlin.io.path.createTempDirectory + +abstract class AbstractProject : AbstractGradleProject(), AutoCloseable { + abstract val gradleProject: GradleProject + abstract val projectName: String + + private lateinit var remoteRepoDir: File + + val dslKind: GradleProject.DslKind = GradleProject.DslKind.KOTLIN + + val scribe = + Scribe( + dslKind = dslKind, + indent = 2, + ) + + val buildCacheDir: File = + createTempDirectory("build-cache").toFile() + + val version: String + get() = fetchSemverProperties().getProperty("version") + + val versionTag: String + get() = fetchSemverProperties().getProperty("versionTag") + + fun git(block: GitInstance.Builder.() -> Unit) { + val builder = GitInstance.Builder(this) + builder.block() + git(builder.build()) + } + + fun git(gitInstance: GitInstance) { + remoteRepoDir = createTempDirectory("remote-repo").toFile() + + val localGit = KGit(gradleProject.rootDir, initializeRepo = InitializeRepo(bare = false, gitInstance.initialBranch)) + val remoteGit = KGit(remoteRepoDir, initializeRepo = InitializeRepo(bare = true, gitInstance.initialBranch)) + + // GHA needs this since no author is configured in the runner + localGit.config.author("Al Gorithm", "al.gori@thm.com") + + localGit.remote.add(remoteGit.git) + + localGit.commit("Initial commit", allowEmptyCommit = true) + + val gitInstanceWriter = + GitInstanceWriter( + localGit = localGit, + gitActionsConfig = gitInstance.actions, + ) + + gitInstanceWriter.write(gitInstance.debugging) + } + + fun cleanAfterAny() { + // The remote repo must be recreated for each test + if (this::remoteRepoDir.isInitialized) { + remoteRepoDir.deleteRecursively() + } + gradleProject.rootDir.resolve(".git").deleteRecursively() + } + + override fun close() { + buildCacheDir.deleteRecursively() + } + + protected open fun fetchSemverProperties(): Properties = + gradleProject.rootDir.resolve(Constants.SEMVER_PROPERTY_PATH) + .let { Properties().apply { load(it.reader()) } } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/projects/GradleProjects.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/GradleProjects.kt new file mode 100644 index 0000000..a84c603 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/GradleProjects.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.projects + +import com.figure.gradle.semver.git.GitInstance +import com.figure.gradle.semver.gradle.build +import com.figure.gradle.semver.gradle.buildAndFail +import com.figure.gradle.semver.gradle.runWithoutExpectations +import org.gradle.testkit.runner.BuildResult +import org.gradle.util.GradleVersion + +class GradleProjects( + private val projects: Map, +) : AutoCloseable { + companion object { + fun gradleProjects(vararg projects: AbstractProject) = GradleProjects(projects.associateBy { it.projectName }) + } + + val versions: List + get() = projects.values.map { it.version } + + val versionTags: List + get() = projects.values.map { it.versionTag } + + fun git(gitInstance: GitInstance) { + projects.values.forEach { it.git(gitInstance) } + } + + fun git(block: GitInstance.Builder.() -> Unit) { + projects.values.forEach { it.git(block) } + } + + fun cleanAfterAny() { + projects.values.forEach { it.cleanAfterAny() } + } + + fun build( + gradleVersion: GradleVersion, + vararg args: String, + ): Map = + projects.values.associateWith { project -> + build(gradleVersion, project.gradleProject.rootDir, *args) + } + + // TODO: Anywhere this is used, check for the specific message, not just BUILD FAILED + fun runWithoutExpectations( + gradleVersion: GradleVersion, + vararg args: String, + ): Map = + projects.values.associateWith { project -> + runWithoutExpectations(gradleVersion, project.gradleProject.rootDir, *args) + } + + fun buildAndFail( + gradleVersion: GradleVersion, + vararg args: String, + ): Map = + projects.values.associateWith { project -> + buildAndFail(gradleVersion, project.gradleProject.rootDir, *args) + } + + override fun close() { + projects.values.forEach { it.close() } + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/projects/RegularProject.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/RegularProject.kt new file mode 100644 index 0000000..a1df57d --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/RegularProject.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.projects + +import com.autonomousapps.kit.GradleProject +import com.autonomousapps.kit.gradle.SettingsScript +import com.figure.gradle.semver.gradle.SemverConfiguration +import com.figure.gradle.semver.gradle.buildCache +import com.figure.gradle.semver.gradle.local +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.gradle.settingsGradle +import com.figure.gradle.semver.plugins.GradlePlugins + +class RegularProject( + override val projectName: String, + private val semver: SemverConfiguration = semver {}, +) : AbstractProject() { + override val gradleProject: GradleProject + get() = build() + + private fun build(): GradleProject = + newGradleProjectBuilder(dslKind).withRootProject { + withBuildScript { + plugins( + GradlePlugins.semverPlugin, + GradlePlugins.kotlinNoApply, + ) + + additions = scribe.use { s -> semver.render(s) } + } + + val settings = + settingsGradle { + buildCache = + buildCache { + local = + local { + directory = buildCacheDir + } + } + } + + settingsScript = + SettingsScript( + additions = scribe.use { s -> settings.render(s) }, + ) + }.write() +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SettingsProject.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SettingsProject.kt new file mode 100644 index 0000000..d2d767a --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SettingsProject.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.projects + +import com.autonomousapps.kit.GradleProject +import com.autonomousapps.kit.gradle.Plugins +import com.autonomousapps.kit.gradle.SettingsScript +import com.figure.gradle.semver.gradle.SemverConfiguration +import com.figure.gradle.semver.gradle.buildCache +import com.figure.gradle.semver.gradle.local +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.gradle.settingsGradle +import com.figure.gradle.semver.plugins.GradlePlugins + +class SettingsProject( + override val projectName: String, + private val semver: SemverConfiguration = semver { }, +) : AbstractProject() { + override val gradleProject: GradleProject + get() = build() + + private fun build(): GradleProject = + newGradleProjectBuilder(dslKind).withRootProject { + val settings = + settingsGradle { + buildCache = + buildCache { + local = + local { + directory = buildCacheDir + } + } + semver = this@SettingsProject.semver + } + + settingsScript = + SettingsScript( + plugins = Plugins(GradlePlugins.semverPlugin), + additions = scribe.use { s -> settings.render(s) }, + ) + + withBuildScript { + plugins(GradlePlugins.kotlinNoApply) + } + }.write() +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SubprojectProject.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SubprojectProject.kt new file mode 100644 index 0000000..28652d6 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/projects/SubprojectProject.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.projects + +import com.autonomousapps.kit.GradleProject +import com.autonomousapps.kit.gradle.SettingsScript +import com.figure.gradle.semver.Constants +import com.figure.gradle.semver.gradle.SemverConfiguration +import com.figure.gradle.semver.gradle.buildCache +import com.figure.gradle.semver.gradle.local +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.gradle.settingsGradle +import com.figure.gradle.semver.plugins.GradlePlugins +import java.util.Properties + +class SubprojectProject( + override val projectName: String, + private val semver: SemverConfiguration = semver {}, +) : AbstractProject() { + override val gradleProject: GradleProject + get() = build() + + private val subprojectName = "subproj" + + private fun build(): GradleProject = + newGradleProjectBuilder(dslKind).withRootProject { + withBuildScript { + plugins(GradlePlugins.kotlinNoApply) + } + + val settings = + settingsGradle { + buildCache = + buildCache { + local = + local { + directory = buildCacheDir + } + } + } + + settingsScript = + SettingsScript( + additions = scribe.use { s -> settings.render(s) }, + ) + }.withSubproject(subprojectName) { + withBuildScript { + plugins(GradlePlugins.semverPlugin, GradlePlugins.kotlinNoApply) + additions = scribe.use { s -> semver.render(s) } + } + }.write() + + override fun fetchSemverProperties(): Properties = + gradleProject.projectDir(subprojectName).toFile().resolve(Constants.SEMVER_PROPERTY_PATH).let { + Properties().apply { load(it.reader()) } + } +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionForMajorVersionSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionForMajorVersionSpec.kt new file mode 100644 index 0000000..49d6b63 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionForMajorVersionSpec.kt @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.SemverProperty +import com.figure.gradle.semver.internal.properties.Stage +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyContain +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverForMajorVersion +import com.figure.gradle.semver.util.GradleArgs.semverModifier +import com.figure.gradle.semver.util.GradleArgs.semverStage +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class CalculateNextVersionForMajorVersionSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "master" + val developmentBranch = "devel" + val featureBranch = "cool-feature" + val releaseBranch = "release/v0" + + context("should not calculate next version for major version") { + test("when value is not an integer") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(releaseBranch) + commit(message = "1 commit on $releaseBranch") + } + } + + // When + val outputs = + projects.runWithoutExpectations( + GradleVersion.current(), + "-P${SemverProperty.ForMajorVersion.property}=not-an-integer", + ).values.map { it.output } + + // Then + outputs shouldOnlyContain "semver.forMajorVersion must be representative of a valid major version line (0, 1, 2, etc.)" + } + + test("when modifier is major and next major version is specified") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(releaseBranch) + commit(message = "1 commit on $releaseBranch") + } + } + + // When + val results = + projects.runWithoutExpectations( + GradleVersion.current(), + semverStage(Stage.Stable), + semverModifier(Modifier.Major), + semverForMajorVersion(0), + ) + + // Then + results.values.map { it.output } shouldOnlyContain "forMajorVersion cannot be used with the 'major' modifier" + } + } + + context("should calculate next version for major version") { + test("on main branch - next minor version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Minor), semverForMajorVersion(0)) + + // Then + projects.versions shouldOnlyHave "0.3.0" + } + + test("on main branch - next patch version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current(), semverForMajorVersion(0)) + + // Then + projects.versions shouldOnlyHave "0.2.6" + } + + test("on development branch - next patch version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Stable), semverForMajorVersion(0)) + + // Then + projects.versions shouldOnlyHave "0.2.6" + } + + test("on feature branch - next patch version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Stable), semverForMajorVersion(0)) + + // Then + projects.versions shouldOnlyHave "0.2.6" + } + + test("on feature branch - new release candidate version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + commit(message = "1 commit on $mainBranch", tag = "1.0.1") + commit(message = "1 commit on $mainBranch", tag = "1.1.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.ReleaseCandidate), semverForMajorVersion(0)) + + // Then + projects.versions shouldOnlyHave "0.2.6-rc.1" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionInNonNominalStateSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionInNonNominalStateSpec.kt new file mode 100644 index 0000000..b58e0ad --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionInNonNominalStateSpec.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.git.Script +import com.figure.gradle.semver.internal.command.GitState +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData +import org.gradle.util.GradleVersion + +class CalculateNextVersionInNonNominalStateSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val featureBranch = "feature-branch" + + context("should calculate next version in non nominal state when") { + withData( + nameFn = { "running ${it.script.scriptFileName}" }, + TestData(Script.CREATE_BISECTING_STATE, "0.2.6-${GitState.BISECTING.description}"), + TestData(Script.CREATE_CHERRY_PICKING_STATE, "0.2.6-${GitState.CHERRY_PICKING.description}"), + TestData(Script.CREATE_MERGING_STATE, "0.2.6-${GitState.MERGING.description}"), + TestData(Script.CREATE_REBASING_STATE, "0.2.6-${GitState.REBASING.description}"), + TestData(Script.CREATE_REVERTING_STATE, "0.2.6-${GitState.REVERTING.description}"), + TestData(Script.CREATE_DETACHED_HEAD_STATE, "0.2.6-${GitState.DETACHED_HEAD.description}"), + ) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + runScript(it.script, mainBranch, featureBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave it.expectedVersion + } + } +}) + +private data class TestData( + val script: Script, + val expectedVersion: String, +) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithBuildMetadataSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithBuildMetadataSpec.kt new file mode 100644 index 0000000..a8b14fb --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithBuildMetadataSpec.kt @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.kotest.shouldOnlyMatch +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.system.OverrideMode +import io.kotest.extensions.system.withEnvironment +import org.gradle.util.GradleVersion + +class CalculateNextVersionWithBuildMetadataSpec : FunSpec({ + val mainBranch = "main" + val featureBranch = "feature/cool/branch" + val developmentBranch = "develop" + + context("should not append build metadata") { + test("when build metadata is invalid") { + // Given + val semver = + semver { + appendBuildMetadata = "invalid" + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("when ${BuildMetadataOptions.NEVER} is specified") { + // Given + val semver = + semver { + appendBuildMetadata = BuildMetadataOptions.NEVER.name + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("when ${BuildMetadataOptions.LOCALLY} is specified but building in CI") { + withEnvironment("CI", "true", mode = OverrideMode.SetOrOverride) { + // Given + val semver = + semver { + appendBuildMetadata = BuildMetadataOptions.LOCALLY.name + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + } + + test("when appendBuildMetadata is set to null") { + // Given + val semver = + semver { + appendBuildMetadata = null + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + } + + context("should append build metadata") { + context("when ${BuildMetadataOptions.ALWAYS} is specified") { + val semver = + semver { + appendBuildMetadata = BuildMetadataOptions.ALWAYS.name + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + + test("and on $mainBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1\+[0-9]{12}""".toRegex() + } + + test("and on $developmentBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1-$developmentBranch.1\+[0-9]{12}""".toRegex() + } + + test("and on $featureBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1-${featureBranch.replace("/", "-")}.1\+[0-9]{12}""".toRegex() + } + } + + context("when ${BuildMetadataOptions.LOCALLY} is specified") { + val semver = + semver { + appendBuildMetadata = BuildMetadataOptions.LOCALLY.name + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + + test("and on $mainBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1\+[0-9]{12}""".toRegex() + } + } + + test("and on $developmentBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1-$developmentBranch.1\+[0-9]{12}""".toRegex() + } + } + + test("and on $featureBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyMatch """1.0.1-${featureBranch.replace("/", "-")}.1\+[0-9]{12}""".toRegex() + } + } + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithModifierSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithModifierSpec.kt new file mode 100644 index 0000000..cf2ffa2 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithModifierSpec.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.SemverProperty +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyContain +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverModifier +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class CalculateNextVersionWithModifierSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "master" + val developmentBranch = "devel" + val featureBranch = "cool-feature" + + context("should not calculate next version") { + test("when modifier is invalid") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(featureBranch) + } + } + + // When / Then + val results = projects.runWithoutExpectations(GradleVersion.current(), "-P${SemverProperty.Modifier.property}=invalid") + + // Then + results.values.map { it.output } shouldOnlyContain "Invalid modifier provided" + } + } + + context("should calculate next version with modifier input") { + test("on main branch - next major version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Major)) + + // Then + projects.versions shouldOnlyHave "2.0.0" + } + + test("on main branch - next minor version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Minor)) + + // Then + projects.versions shouldOnlyHave "1.1.0" + } + + test("on main branch - next patch version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Patch)) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("on feature branch - next major version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Major)) + + // Then + projects.versions shouldOnlyHave "2.0.0-cool-feature.1" + } + + test("on feature branch - next minor version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Minor)) + + // Then + projects.versions shouldOnlyHave "1.1.0-cool-feature.1" + } + + test("on feature branch - next patch version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverModifier(Modifier.Patch)) + + // Then + projects.versions shouldOnlyHave "1.0.1-cool-feature.1" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithStageSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithStageSpec.kt new file mode 100644 index 0000000..cb33fd1 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithStageSpec.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.internal.properties.SemverProperty +import com.figure.gradle.semver.internal.properties.Stage +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyContain +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverStage +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class CalculateNextVersionWithStageSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val featureBranch = "feature/cool/branch" + val developmentBranch = "dev" + + context("should not calculate next version") { + test("when stage is invalid") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(mainBranch) + } + } + + // When + val results = projects.runWithoutExpectations(GradleVersion.current(), "-P${SemverProperty.Stage.property}=invalid") + + // Then + results.values.map { it.output } shouldOnlyContain "BUILD FAILED" + } + } + + context("should calculate next version with stage input") { + test("on feature branch - new alpha version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Alpha)) + + // Then + projects.versions shouldOnlyContain "1.0.1-alpha.1" + } + + test("on feature branch - next alpha version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2-alpha.1") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Alpha)) + + // Then + projects.versions shouldOnlyContain "1.0.2-alpha.2" + } + + test("on feature branch - new rc version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2-alpha.3") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.ReleaseCandidate)) + + // Then + projects.versions shouldOnlyContain "1.0.3-rc.1" + } + + test("on develop branch - new release version") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2-rc.5") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Release)) + + // Then + projects.versions shouldOnlyContain "1.0.3-release.1" + } + + test("on develop branch - new final version where last tag is from a branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2") + commit(message = "2 commit on $mainBranch", tag = "1.0.3-my-awesome-feature.5") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Final)) + + // Then + projects.versions shouldOnlyContain "1.0.3-final.1" + } + + test("on main branch - next stable version where last tag is from a branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2") + commit(message = "2 commit on $mainBranch", tag = "1.0.3-my-awesome-feature.5") + } + } + + // When + projects.build(GradleVersion.current(), semverStage(Stage.Stable)) + + // Then + projects.versions shouldOnlyContain "1.0.3" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithoutInputsSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithoutInputsSpec.kt new file mode 100644 index 0000000..113db4b --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/CalculateNextVersionWithoutInputsSpec.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class CalculateNextVersionWithoutInputsSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val developmentBranch = "develop" + val featureBranch = "myname/sc-123456/my-awesome-feature" + + context("should calculate next version without inputs") { + test("on main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + checkout(mainBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("on development branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-develop.1" + } + + test("on development branch with latest development tag") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch", tag = "1.0.1-develop.1") + commit(message = "2 commit on $developmentBranch") + commit(message = "3 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-develop.3" + } + + test("on feature branch off development branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + commit(message = "2 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-myname-sc-123456-my-awesome-feature.2" + } + + test("on feature branch off main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + commit(message = "2 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-myname-sc-123456-my-awesome-feature.2" + } + + test("for develop branch after committing to feature branch and switching back to develop") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + + checkout(developmentBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-develop.1" + } + + test("next branch version where last tag is prerelease") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.2") + commit(message = "1 commit on $mainBranch", tag = "1.0.3-alpha.1") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.3-myname-sc-123456-my-awesome-feature.1" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingLocalBranchSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingLocalBranchSpec.kt new file mode 100644 index 0000000..ba03432 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingLocalBranchSpec.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class MissingLocalBranchSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "master" + val developmentBranch = "dev" + val featureBranch = "feature-3" + + test("on development branch, missing local main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + removeLocalBranch(mainBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6-dev.1" + } + + test("on feature branch, missing local development branch") { + // Given + projects.git { + initialBranch = mainBranch + debugging = true + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + + removeLocalBranch(developmentBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6-feature-3.1" + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingRemoteBranchSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingRemoteBranchSpec.kt new file mode 100644 index 0000000..7ce3875 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/MissingRemoteBranchSpec.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class MissingRemoteBranchSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val developmentBranch = "develop" + val featureBranch = "feature-2" + + test("on main, missing remote main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + + removeRemoteBranch(mainBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6" + } + + test("on develop, missing remote main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + + removeRemoteBranch(mainBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6-develop.1" + } + + test("on feature branch, missing remote main branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + + removeRemoteBranch(mainBranch) + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6-feature-2.1" + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UninitializedRepositorySpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UninitializedRepositorySpec.kt new file mode 100644 index 0000000..7791260 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UninitializedRepositorySpec.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UninitializedRepositorySpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + test("should build on uninitialized repository") { + // When + projects.build(GradleVersion.current()) + + // Then + // This assumes that the initial version is 0.0.0 and that the first build will generate a patch version + projects.versions shouldOnlyHave "0.0.1-UNINITIALIZED-REPO" + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseAppendBuildMetadataPropertySpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseAppendBuildMetadataPropertySpec.kt new file mode 100644 index 0000000..6074cc7 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseAppendBuildMetadataPropertySpec.kt @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.kotest.shouldOnlyMatch +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverAppendBuildMetadata +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import io.kotest.extensions.system.OverrideMode +import io.kotest.extensions.system.withEnvironment +import org.gradle.util.GradleVersion + +class UseAppendBuildMetadataPropertySpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val developmentBranch = "develop" + val featureBranch = "patch-1" + + context("should not use override version") { + test("when override version is invalid") { + // Given + val appendBuildMetadataOption = "invalid" + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("when ${BuildMetadataOptions.NEVER} is specified") { + // Given + val appendBuildMetadataOption = BuildMetadataOptions.NEVER.name + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("when ${BuildMetadataOptions.LOCALLY} is specified but building in CI") { + withEnvironment("CI", "true", mode = OverrideMode.SetOrOverride) { + // Given + val appendBuildMetadataOption = BuildMetadataOptions.LOCALLY.name + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + } + } + + context("should append build metadata") { + context("when ${BuildMetadataOptions.ALWAYS} is specified") { + val appendBuildMetadataOption = BuildMetadataOptions.ALWAYS.name + + test("and on $mainBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1\+[0-9]{12}""".toRegex() + } + + test("and on $developmentBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1-$developmentBranch.1\+[0-9]{12}""".toRegex() + } + + test("and on $featureBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1-${featureBranch.replace("/", "-")}.1\+[0-9]{12}""".toRegex() + } + } + + context("when ${BuildMetadataOptions.LOCALLY} is specified") { + val appendBuildMetadataOption = BuildMetadataOptions.LOCALLY.name + + test("and on $mainBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1\+[0-9]{12}""".toRegex() + } + } + + test("and on $developmentBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1-$developmentBranch.1\+[0-9]{12}""".toRegex() + } + } + + test("and on $featureBranch branch") { + withEnvironment("CI", null, mode = OverrideMode.SetOrOverride) { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "1.0.0") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverAppendBuildMetadata(appendBuildMetadataOption)) + + // Then + projects.versions shouldOnlyMatch """1.0.1-${featureBranch.replace("/", "-")}.1\+[0-9]{12}""".toRegex() + } + } + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseDevelopmentBranchSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseDevelopmentBranchSpec.kt new file mode 100644 index 0000000..0d7c433 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseDevelopmentBranchSpec.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UseDevelopmentBranchSpec : FunSpec({ + val mainBranch = "main" + val developBranch = "staging" + val featureBranch = "feature-1" + + context("should use development branch") { + val semver = + semver { + this.mainBranch = mainBranch + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + + test("on $developBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch", tag = "1.0.0") + checkout(developBranch) + commit("1 commit on $developBranch") + } + } + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-$developBranch.1" + } + + test("on $featureBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch", tag = "1.0.0") + checkout(developBranch) + commit("1 commit on $developBranch") + checkout(featureBranch) + commit("1 commit on $featureBranch") + } + } + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-$featureBranch.2" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseInitialVersionSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseInitialVersionSpec.kt new file mode 100644 index 0000000..c2a5b22 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseInitialVersionSpec.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.github.z4kn4fein.semver.nextPatch +import io.github.z4kn4fein.semver.toVersion +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UseInitialVersionSpec : FunSpec({ + val mainBranch = "main" + val developmentBranch = "develop" + val featureBranch = "patch-1" + + val initialVersion = "1.1.1" + val expectedStableVersion = initialVersion.toVersion().nextPatch().toString() + + context("should use initial version") { + val semver = + semver { + this.initialVersion = initialVersion + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + + test("on main branch") { + // Given + // The default initial value is "0.0.0" which is supplied by the plugin + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave expectedStableVersion + } + + test("on development branch") { + // Given + // The default initial value is "0.0.0" which is supplied by the plugin + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch") + + checkout(developmentBranch) + commit("1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "$expectedStableVersion-develop.1" + } + + test("on feature branch") { + // Given + // The default initial value is "0.0.0" which is supplied by the plugin + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch") + + checkout(featureBranch) + commit("1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "$expectedStableVersion-patch-1.1" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseMainBranchSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseMainBranchSpec.kt new file mode 100644 index 0000000..5af0d9e --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseMainBranchSpec.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.gradle.semver +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UseMainBranchSpec : FunSpec({ + val mainBranch = "trunk" + val developBranch = "develop" + val featureBranch = "feature-1" + + context("should use main branch") { + val semver = + semver { + this.mainBranch = mainBranch + } + + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project", semver = semver), + SettingsProject(projectName = "settings-project", semver = semver), + SubprojectProject(projectName = "subproject-project", semver = semver), + ), + ) + + test("on $mainBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch", tag = "1.0.0") + } + } + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1" + } + + test("on $developBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch", tag = "1.0.0") + checkout(developBranch) + commit("1 commit on $developBranch") + } + } + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-$developBranch.1" + } + + test("on $featureBranch branch") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit("1 commit on $mainBranch", tag = "1.0.0") + checkout(featureBranch) + commit("1 commit on $featureBranch") + } + } + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "1.0.1-$featureBranch.1" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseOverrideVersionSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseOverrideVersionSpec.kt new file mode 100644 index 0000000..70e7617 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseOverrideVersionSpec.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyContain +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverOverrideVersion +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UseOverrideVersionSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + val developmentBranch = "develop" + val featureBranch = "patch-1" + + context("should not use override version") { + test("when override version is invalid") { + // Given + val givenVersion = "im-not-a-version" + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch") + } + } + + // When + val results = projects.runWithoutExpectations(GradleVersion.current(), semverOverrideVersion(givenVersion)) + + // Then + results.values.map { it.output } shouldOnlyContain "BUILD FAILED" + } + } + + context("should use override version") { + test("on main branch") { + // Given + val givenVersion = "9.9.9" + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverOverrideVersion(givenVersion)) + + // Then + projects.versions shouldOnlyHave givenVersion + } + + test("on development branch") { + // Given + val givenVersion = "9.9.9-beta.1" + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch") + + checkout(developmentBranch) + commit(message = "1 commit on $developmentBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverOverrideVersion(givenVersion)) + + // Then + projects.versions shouldOnlyHave givenVersion + } + + test("on feature branch") { + // Given + val givenVersion = "9.9.9-alpha.1" + + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch") + + checkout(featureBranch) + commit(message = "1 commit on $featureBranch") + } + } + + // When + projects.build(GradleVersion.current(), semverOverrideVersion(givenVersion)) + + // Then + projects.versions shouldOnlyHave givenVersion + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseTagPrefixSpec.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseTagPrefixSpec.kt new file mode 100644 index 0000000..8168f0b --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/specs/UseTagPrefixSpec.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.specs + +import com.figure.gradle.semver.kotest.GradleProjectsExtension +import com.figure.gradle.semver.kotest.shouldOnlyHave +import com.figure.gradle.semver.projects.RegularProject +import com.figure.gradle.semver.projects.SettingsProject +import com.figure.gradle.semver.projects.SubprojectProject +import com.figure.gradle.semver.util.GradleArgs.semverTagPrefix +import io.kotest.core.extensions.install +import io.kotest.core.spec.style.FunSpec +import org.gradle.util.GradleVersion + +class UseTagPrefixSpec : FunSpec({ + val projects = + install( + GradleProjectsExtension( + RegularProject(projectName = "regular-project"), + SettingsProject(projectName = "settings-project"), + SubprojectProject(projectName = "subproject-project"), + ), + ) + + val mainBranch = "main" + + context("should use tag prefix") { + test("on main branch with default tag prefix") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + } + } + + // When + projects.build(GradleVersion.current()) + + // Then + projects.versions shouldOnlyHave "0.2.6" + projects.versionTags shouldOnlyHave "v0.2.6" + } + + test("on feature branch with provided tag prefix") { + // Given + projects.git { + initialBranch = mainBranch + actions = + actions { + commit(message = "1 commit on $mainBranch", tag = "0.2.5") + } + } + + // When + projects.build(GradleVersion.current(), semverTagPrefix("Nov Release ")) + + // Then + projects.versions shouldOnlyHave "0.2.6" + projects.versionTags shouldOnlyHave "Nov Release 0.2.6" + } + } +}) diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/util/GradleArgs.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/util/GradleArgs.kt new file mode 100644 index 0000000..21832bb --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/util/GradleArgs.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.util + +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.SemverProperty +import com.figure.gradle.semver.internal.properties.Stage + +/** + * The parameters must be provided in the format: `-P=` and not `-P =`. + */ +internal object GradleArgs { + fun semverStage(stage: Stage) = "-P${SemverProperty.Stage.property}=${stage.value}" + + fun semverModifier(modifier: Modifier) = "-P${SemverProperty.Modifier.property}=${modifier.value}" + + fun semverTagPrefix(tagPrefix: String) = "-P${SemverProperty.TagPrefix.property}=$tagPrefix" + + fun semverForTesting(forTesting: Boolean) = "-P${SemverProperty.ForTesting.property}=$forTesting" + + fun semverOverrideVersion(overrideVersion: String) = "-P${SemverProperty.OverrideVersion.property}=$overrideVersion" + + fun semverForMajorVersion(forMajorVersion: Int) = "-P${SemverProperty.ForMajorVersion.property}=$forMajorVersion" + + fun semverAppendBuildMetadata(buildMetadataOptions: String) = "-P${SemverProperty.AppendBuildMetadata.property}=$buildMetadataOptions" +} diff --git a/src/functionalTest/kotlin/com/figure/gradle/semver/util/file.kt b/src/functionalTest/kotlin/com/figure/gradle/semver/util/file.kt new file mode 100644 index 0000000..9fc5e05 --- /dev/null +++ b/src/functionalTest/kotlin/com/figure/gradle/semver/util/file.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.util + +import java.io.File + +private fun String.toFile() = File(this) + +fun resolveResource(resourcePath: String): File = + Thread.currentThread().contextClassLoader.getResource(resourcePath)?.file?.toFile() + ?: throw IllegalArgumentException("Resource not found: $resourcePath") diff --git a/src/functionalTest/resources/scripts/create_bisecting_state.sh b/src/functionalTest/resources/scripts/create_bisecting_state.sh new file mode 100644 index 0000000..fb1541f --- /dev/null +++ b/src/functionalTest/resources/scripts/create_bisecting_state.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 2: Create a bug in the code (for demonstration purposes) +echo "Bug introduced" >> file.txt +git add file.txt +git commit -m "Bug commit" + +# Step 3: Start the bisecting process +git bisect start + +# Step 4: Mark the current commit as bad +git bisect bad + +# Step 5: Mark a known good commit (e.g., the initial commit) +git bisect good HEAD~1 diff --git a/src/functionalTest/resources/scripts/create_cherry_picking_state.sh b/src/functionalTest/resources/scripts/create_cherry_picking_state.sh new file mode 100644 index 0000000..a55648b --- /dev/null +++ b/src/functionalTest/resources/scripts/create_cherry_picking_state.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 +main_branch=$2 +feature_branch=$3 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 3: Create a feature branch +git checkout -b "$feature_branch" + +# Step 4: Modify the file in the feature branch +echo "Feature branch change" >> file.txt +git add file.txt +git commit -m "Feature branch commit" + +# Step 5: Switch back to the main branch +git checkout "$main_branch" + +# Step 6: Modify the same line in the file in the main branch +echo "Main branch change" >> file.txt +git add file.txt +git commit -m "Main branch commit" + +# Step 7: Cherry-pick the commit from the feature branch +git cherry-pick "$(git rev-parse "$feature_branch")" diff --git a/src/functionalTest/resources/scripts/create_detached_head_state.sh b/src/functionalTest/resources/scripts/create_detached_head_state.sh new file mode 100644 index 0000000..219963a --- /dev/null +++ b/src/functionalTest/resources/scripts/create_detached_head_state.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 2: Make some changes in the main branch +echo "Changes in main branch" >> file.txt +git add file.txt +git commit -m "Commit in main branch" + +# Step 3: Switch back to the initial commit (detached head state) +git checkout HEAD~1 diff --git a/src/functionalTest/resources/scripts/create_merging_state.sh b/src/functionalTest/resources/scripts/create_merging_state.sh new file mode 100644 index 0000000..b02eec0 --- /dev/null +++ b/src/functionalTest/resources/scripts/create_merging_state.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 +main_branch=$2 +feature_branch=$3 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 3: Create a feature branch +git checkout -b "$feature_branch" + +# Step 4: Modify the file in the feature branch +echo "Feature branch change" >> file.txt +git add file.txt +git commit -m "Feature branch commit" + +# Step 5: Switch back to the main branch +git checkout "$main_branch" + +# Step 6: Modify the same line in the file in the main branch +echo "Main branch change" > file.txt +git add file.txt +git commit -m "Main branch commit" + +# Step 7: Attempt to rebase the feature branch onto the main branch +git merge "$feature_branch" diff --git a/src/functionalTest/resources/scripts/create_rebasing_state.sh b/src/functionalTest/resources/scripts/create_rebasing_state.sh new file mode 100644 index 0000000..5721957 --- /dev/null +++ b/src/functionalTest/resources/scripts/create_rebasing_state.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 +main_branch=$2 +feature_branch=$3 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 3: Create a feature branch +git checkout -b "$feature_branch" + +# Step 4: Modify the file in the feature branch +echo "Feature branch change" >> file.txt +git add file.txt +git commit -m "Feature branch commit" + +# Step 5: Switch back to the main branch +git checkout "$main_branch" + +# Step 6: Modify the same line in the file in the main branch +echo "Main branch change" > file.txt +git add file.txt +git commit -m "Main branch commit" + +# Step 7: Attempt to rebase the feature branch onto the main branch +git rebase "$feature_branch" diff --git a/src/functionalTest/resources/scripts/create_reverting_state.sh b/src/functionalTest/resources/scripts/create_reverting_state.sh new file mode 100644 index 0000000..b0b1ce6 --- /dev/null +++ b/src/functionalTest/resources/scripts/create_reverting_state.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Step 0: Assume the git directory is initialized and switch to the directory +repositoryDir=$1 + +cd "$repositoryDir" || exit + +# Step 1: Create and commit an initial file +echo "This is file.txt" > file.txt +git add file.txt +git commit -m "Initial commit" + +# Step 2: Make a change to the file +echo "Change in file.txt" >> file.txt +git add file.txt +git commit -m "Commit to be reverted" + +# Step 4: Identify the commit hash to be reverted +commitToRevert=$(git rev-parse HEAD) + +# Step 5: Start the revert process +git revert -n "$commitToRevert" + +# Step 6: You are now in the middle of the revert. You can resolve conflicts if any and continue the revert process. +# For demonstration purposes, let's simulate a conflict +echo "Conflict content" > file.txt +git add file.txt diff --git a/src/main/kotlin/com/figure/gradle/semver/Constants.kt b/src/main/kotlin/com/figure/gradle/semver/Constants.kt new file mode 100644 index 0000000..0aa4774 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/Constants.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver + +object Constants { + const val SEMVER_PROPERTY_PATH = "build/semver/semver.properties" +} diff --git a/src/main/kotlin/com/figure/gradle/semver/SemverExtension.kt b/src/main/kotlin/com/figure/gradle/semver/SemverExtension.kt index b66c114..d06e363 100644 --- a/src/main/kotlin/com/figure/gradle/semver/SemverExtension.kt +++ b/src/main/kotlin/com/figure/gradle/semver/SemverExtension.kt @@ -1,120 +1,44 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package com.figure.gradle.semver -import com.figure.gradle.semver.external.BranchMatchingConfiguration -import com.figure.gradle.semver.external.VersionCalculatorStrategy -import com.figure.gradle.semver.external.VersionModifier -import com.figure.gradle.semver.internal.semver.VersionCalculatorConfig -import com.figure.gradle.semver.internal.semver.versionModifierFromString -import com.figure.gradle.semver.internal.valuesources.gitCalculateSemverProvider -import net.swiftzer.semver.SemVer -import org.gradle.api.Project -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ListProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property -import org.gradle.api.provider.ProviderFactory -import org.gradle.kotlin.dsl.listProperty -import org.gradle.kotlin.dsl.property -import javax.inject.Inject - -abstract class SemverExtension @Inject constructor( - objects: ObjectFactory, - project: Project, - providerFactory: ProviderFactory, -) { - /** - * This version invocation takes place at project build time of the project that is utilizing this plugin - * The version is not calculated until a build happens that requires `semver.version` - */ - val version: String by lazy { - providerFactory.gitCalculateSemverProvider( - gitDir = gitDir, - tagPrefix = tagPrefix, - initialVersion = initialVersion.get().toString(), - overrideVersion = overrideVersion.orNull?.toString(), - versionStrategy = versionStrategy, - versionModifier = versionModifier, - ).get() - } - - val versionTagName: String by lazy { calculateVersionTagName() } - - // TODO: For v2, see if rootRepoDirectory can be specified instead (without causing Gradle issues) where - // a semver task relies on the build task to complete. Maybe needs specified as a string instead of File to - // work? Use Git.open(rootRepoDirectory) to create the Git object then. - internal val gitDir: Property = - objects.property() - .convention("${project.rootProject.rootDir.path}/.git") - - private val tagPrefix: Property = - objects.property() - .convention(VersionCalculatorConfig.DEFAULT_TAG_PREFIX) - - private val initialVersion: Property = - objects.property() - .convention(VersionCalculatorConfig.DEFAULT_VERSION) - - private val versionStrategy: ListProperty = - objects.listProperty() - .convention(null) - - private val overrideVersion: Property = - objects.property() - .convention(null) - - private val versionModifier: Property = - objects.property() - .convention { nextPatch() } - - fun gitDir(gitDir: String) { - this.gitDir.set(gitDir) - } - - fun tagPrefix(prefix: String) { - if (overrideVersion.orNull != null) { - throw IllegalArgumentException( - """ - |Cannot set semver tagPrefix after override version has been set. - | The override version depends on the tagPrefix. Set the tagPrefix first. - """.trimMargin().replace("\n", ""), - ) - } - this.tagPrefix.set(prefix) - } - - fun initialVersion(version: String?) { - version?.also { - this.initialVersion.set(SemVer.parse(it)) - } - } - - fun overrideVersion(version: String) { - this.overrideVersion.set(possiblyPrefixVersion(version, tagPrefix.get())) - } - - fun versionModifier(modifier: VersionModifier) { - this.versionModifier.set(modifier) - } - - fun buildVersionModifier(modifier: String): VersionModifier { - return versionModifierFromString(modifier) - } - - fun versionCalculatorStrategy(strategy: VersionCalculatorStrategy) { - this.versionStrategy.set(strategy) - } - private fun calculateVersionTagName(): String { - return tagPrefix.map { prefix -> "$prefix$version" }.get() - } +/** + * Configuration for the Semver Settings Plugin that enables: + * - Manually setting the rootProjectDir + * - An initial version + * - The main branch, if not `main` or `master` + * - The development branch if not `develop`, `devel`, or `dev` + * + */ +interface SemverExtension { + val rootProjectDir: RegularFileProperty + val initialVersion: Property + val mainBranch: Property + val developmentBranch: Property + val appendBuildMetadata: Property - private fun possiblyPrefixVersion(version: String, prefix: String): SemVer { - return SemVer.parse(version.trimMargin(prefix)) // fail fast, don't let an invalid version propagate to runtime + companion object { + const val NAME = "semver" } } + +fun Settings.semver(configure: SemverExtension.() -> Unit): Unit = + (this as ExtensionAware).extensions.configure(SemverExtension.NAME, configure) diff --git a/src/main/kotlin/com/figure/gradle/semver/SemverPlugin.kt b/src/main/kotlin/com/figure/gradle/semver/SemverPlugin.kt index 1a972c7..e15e1ed 100644 --- a/src/main/kotlin/com/figure/gradle/semver/SemverPlugin.kt +++ b/src/main/kotlin/com/figure/gradle/semver/SemverPlugin.kt @@ -1,47 +1,93 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. +/* + * Copyright (C) 2024 Figure Technologies * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ - package com.figure.gradle.semver -import com.figure.gradle.semver.internal.tasks.CreateAndPushVersionTagTask -import com.figure.gradle.semver.internal.tasks.CurrentSemverTask -import com.figure.gradle.semver.internal.tasks.GenerateVersionFileTask +import com.figure.gradle.semver.internal.calculator.VersionFactoryContext +import com.figure.gradle.semver.internal.calculator.versionFactory +import com.figure.gradle.semver.internal.extensions.extensions +import com.figure.gradle.semver.internal.extensions.providers +import com.figure.gradle.semver.internal.extensions.rootDir +import com.figure.gradle.semver.internal.logging.registerPostBuildVersionLogMessage +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.internal.properties.appendBuildMetadata +import com.figure.gradle.semver.internal.properties.forMajorVersion +import com.figure.gradle.semver.internal.properties.forTesting +import com.figure.gradle.semver.internal.properties.modifier +import com.figure.gradle.semver.internal.properties.overrideVersion +import com.figure.gradle.semver.internal.properties.stage +import com.figure.gradle.semver.internal.properties.tagPrefix +import com.figure.gradle.semver.internal.writer.writeVersionToPropertiesFile import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.PluginAware import org.gradle.kotlin.dsl.create -import org.gradle.kotlin.dsl.register -import java.io.File -import java.nio.file.Files -class SemverPlugin : Plugin { - override fun apply(project: Project) { - val semver = project.extensions.create("semver") - - project.tasks.register("currentSemver") { - version.set(semver.version) - versionTagName.set(semver.versionTagName) - } +class SemverPlugin : Plugin { + override fun apply(target: PluginAware) { + val semverExtension = + target.extensions.create("semver").apply { + initialVersion.convention("0.0.0") + appendBuildMetadata.convention("") + } - project.tasks.register("generateVersionFile") { - // Ensure the build directory exists first - if (!project.buildDir.exists()) { - Files.createDirectory(project.buildDir.toPath()) + when (target) { + is Settings -> { + target.gradle.settingsEvaluated { + val nextVersion = target.calculateVersion(semverExtension) + target.gradle.beforeProject { + it.version = nextVersion + } + } } - val versionFile = File("${project.buildDir}/semver/version.txt") + is Project -> { + target.afterEvaluate { + val nextVersion = target.calculateVersion(semverExtension) + target.version = nextVersion + } + } - destination.fileValue(versionFile) - version.set(semver.version) - versionTagName.set(semver.versionTagName) + else -> error("Not a project or settings") } + } - project.tasks.register("createAndPushVersionTag") { - this.versionTagName.set(semver.versionTagName) - this.gitDir.set(project.file(semver.gitDir.get())) - } + private fun PluginAware.calculateVersion(semverExtension: SemverExtension): String { + val versionFactoryContext = + VersionFactoryContext( + initialVersion = semverExtension.initialVersion.get(), + stage = this.stage.get(), + modifier = this.modifier.get(), + forTesting = this.forTesting.get(), + overrideVersion = this.overrideVersion.orNull, + forMajorVersion = this.forMajorVersion.orNull, + rootDir = semverExtension.rootProjectDir.getOrElse { this.rootDir }.asFile, + mainBranch = semverExtension.mainBranch.orNull, + developmentBranch = semverExtension.developmentBranch.orNull, + appendBuildMetadata = + (appendBuildMetadata.takeIf { it.isPresent } ?: semverExtension.appendBuildMetadata) + .map { BuildMetadataOptions.from(it, BuildMetadataOptions.NEVER) } + .get(), + ) + + val nextVersion = this.providers.versionFactory(versionFactoryContext).get() + + this.registerPostBuildVersionLogMessage(nextVersion) + this.writeVersionToPropertiesFile(nextVersion, tagPrefix.get()) + + return nextVersion } } diff --git a/src/main/kotlin/com/figure/gradle/semver/external/BranchMatchingConfiguration.kt b/src/main/kotlin/com/figure/gradle/semver/external/BranchMatchingConfiguration.kt deleted file mode 100644 index 03b5db8..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/external/BranchMatchingConfiguration.kt +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.external - -import com.figure.gradle.semver.internal.git.GitRef -import net.swiftzer.semver.SemVer - -typealias VersionCalculatorStrategy = List -typealias VersionModifier = SemVer.() -> SemVer // TODO: Does this need to be a method? -typealias VersionQualifier = SemverContext.(current: GitRef.Branch) -> Pair - -// TODO: Clean this up for v2. -// - The end user shouldn't have to know about the SemVer object. -// - VersionQualifier is probably more complicated than necessary. -// - Support RC versions properly. Should this be a VersionModifier? -data class BranchMatchingConfiguration( - val regex: Regex, - val targetBranch: GitRef.Branch, - val versionQualifier: VersionQualifier, - val versionModifier: VersionModifier = { nextPatch() }, -) - -fun mainBasedFlowVersionCalculatorStrategy(versionModifier: VersionModifier): VersionCalculatorStrategy = - buildFlowVersionCalculatorStrategy(GitRef.Branch.MAIN, versionModifier) - -fun masterBasedFlowVersionCalculatorStrategy(versionModifier: VersionModifier): VersionCalculatorStrategy = - buildFlowVersionCalculatorStrategy(GitRef.Branch.MASTER, versionModifier) - -private fun buildFlowVersionCalculatorStrategy( - targetBranch: GitRef.Branch, - versionModifier: VersionModifier, -): VersionCalculatorStrategy = - listOf( - BranchMatchingConfiguration( - regex = """^${targetBranch.name}$""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { - PreReleaseLabel.EMPTY to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - BranchMatchingConfiguration( - regex = """^develop$""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { currentBranch -> - preReleaseWithCommitCount( - currentBranch = currentBranch, - targetBranch = targetBranch, - label = "beta", - ) to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - BranchMatchingConfiguration( - regex = """^rc/.*""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { currentBranch -> - preReleaseWithCommitCount( - currentBranch = currentBranch, - targetBranch = targetBranch, - label = "rc", - ) to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - // This one must be last so the other configurations get matched first - BranchMatchingConfiguration( - regex = """.*""".toRegex(), - targetBranch = GitRef.Branch.DEVELOP, - versionQualifier = { currentBranch -> - preReleaseWithCommitCount( - currentBranch = currentBranch, - targetBranch = targetBranch, - label = currentBranch.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - ) - -fun mainBasedFlatVersionCalculatorStrategy(versionModifier: VersionModifier): VersionCalculatorStrategy = - buildFlatVersionCalculatorStrategy(GitRef.Branch.MAIN, versionModifier) - -fun masterBasedFlatVersionCalculatorStrategy(versionModifier: VersionModifier): VersionCalculatorStrategy = - buildFlatVersionCalculatorStrategy(GitRef.Branch.MASTER, versionModifier) - -private fun buildFlatVersionCalculatorStrategy( - targetBranch: GitRef.Branch, - versionModifier: VersionModifier, -): VersionCalculatorStrategy = - listOf( - BranchMatchingConfiguration( - regex = """^${targetBranch.name}$""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { PreReleaseLabel.EMPTY to BuildMetadataLabel.EMPTY }, - versionModifier = versionModifier, - ), - BranchMatchingConfiguration( - regex = """^rc/.*""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { currentBranch -> - preReleaseWithCommitCount( - currentBranch = currentBranch, - targetBranch = targetBranch, - label = "rc", - ) to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - BranchMatchingConfiguration( - regex = """.*""".toRegex(), - targetBranch = targetBranch, - versionQualifier = { - preReleaseWithCommitCount( - currentBranch = it, - targetBranch = targetBranch, - label = it.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier = versionModifier, - ), - ) diff --git a/src/main/kotlin/com/figure/gradle/semver/external/ContextProviderOperations.kt b/src/main/kotlin/com/figure/gradle/semver/external/ContextProviderOperations.kt deleted file mode 100644 index 4a16d2d..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/external/ContextProviderOperations.kt +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.external - -import com.figure.gradle.semver.internal.git.GitRef -import net.swiftzer.semver.SemVer - -interface ContextProviderOperations { - fun currentBranch(): GitRef.Branch? - - fun branchVersion(currentBranch: GitRef.Branch, targetBranch: GitRef.Branch): Result - - fun commitsSinceBranchPoint(currentBranch: GitRef.Branch, targetBranch: GitRef.Branch): Result -} diff --git a/src/main/kotlin/com/figure/gradle/semver/external/Labels.kt b/src/main/kotlin/com/figure/gradle/semver/external/Labels.kt deleted file mode 100644 index f70c734..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/external/Labels.kt +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.external - -@JvmInline -value class PreReleaseLabel(val value: String) { - companion object { - val EMPTY = PreReleaseLabel("") - } -} - -@JvmInline -value class BuildMetadataLabel(val value: String) { - companion object { - val EMPTY = BuildMetadataLabel("") - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/external/SemverContext.kt b/src/main/kotlin/com/figure/gradle/semver/external/SemverContext.kt deleted file mode 100644 index cb9db66..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/external/SemverContext.kt +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.external - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.internal.semverWarn -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging - -private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) - -interface SemverContext { - val ops: ContextProviderOperations - - fun preReleaseWithCommitCount( - currentBranch: GitRef.Branch, - targetBranch: GitRef.Branch, - label: String, - ) = PreReleaseLabel( - value = - ops.commitsSinceBranchPoint(currentBranch, targetBranch).fold( - onSuccess = { - "$label.$it" - }, - onFailure = { - log.semverWarn("Unable to calculate commits since branch point on current $currentBranch") - label - }, - ), - ) -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/GithubActions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/GithubActions.kt deleted file mode 100644 index 7b68529..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/GithubActions.kt +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal - -internal fun githubActionsBuild(): Boolean = - System.getenv("GITHUB_ACTIONS") - ?.let { it == "true" } ?: false - -internal fun pullRequestEvent(): Boolean = - System.getenv("GITHUB_EVENT_NAME") - ?.let { it == "pull_request" } ?: false - -internal fun pullRequestHeadRef(): String? = - System.getenv("GITHUB_HEAD_REF") diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/LogExt.kt b/src/main/kotlin/com/figure/gradle/semver/internal/LogExt.kt deleted file mode 100644 index 8472483..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/LogExt.kt +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal - -import org.gradle.api.logging.Logger - -private const val LOG_ERROR_PREFIX = "[gradle-semver-plugin] ERROR " -private const val LOG_QUIET_PREFIX = "[gradle-semver-plugin] QUIET " -private const val LOG_WARN_PREFIX = "[gradle-semver-plugin] WARN " -private const val LOG_LIFECYCLE_PREFIX = "> Semver: " -private const val LOG_INFO_PREFIX = "[gradle-semver-plugin] INFO " -private const val LOG_DEBUG_PREFIX = "[gradle-semver-plugin] DEBUG " - -private fun String.colored(c: String) = "$c$this\u001B[0m" - -internal fun String.darkgray() = this.colored("\u001B[30m") - -internal fun String.red() = this.colored("\u001B[31m") - -internal fun String.green() = this.colored("\u001B[32m") - -internal fun String.yellow() = this.colored("\u001B[33m") - -internal fun String.purple() = this.colored("\u001B[35m") - -internal fun String.lightgray() = this.colored("\u001B[37m") - -internal fun String.bold() = this.colored("\u001B[1m") - -internal fun Logger.semverError(message: String) = this.error(LOG_ERROR_PREFIX.bold() + message.red()) - -internal fun Logger.semverError( - message: String, - e: Throwable, -) = this.error(LOG_ERROR_PREFIX.bold() + message.red(), e) - -internal fun Logger.semverQuiet(message: String) = this.quiet(LOG_QUIET_PREFIX.bold() + message.lightgray()) - -internal fun Logger.semverWarn(message: String) = this.warn(LOG_WARN_PREFIX.bold() + message.yellow()) - -internal fun Logger.semverLifecycle(message: String) = this.lifecycle(LOG_LIFECYCLE_PREFIX.bold() + message.purple()) - -internal fun Logger.semverInfo(message: String) = this.info(LOG_INFO_PREFIX.bold() + message.green()) - -internal fun Logger.semverDebug(message: String) = this.debug(LOG_DEBUG_PREFIX.bold() + message.darkgray()) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/BranchVersionCalculator.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/BranchVersionCalculator.kt new file mode 100644 index 0000000..b1e849f --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/BranchVersionCalculator.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import com.figure.gradle.semver.internal.command.KGit +import com.figure.gradle.semver.internal.extensions.appendBuildMetadata +import com.figure.gradle.semver.internal.extensions.sanitizedWithoutPrefix +import io.github.z4kn4fein.semver.Version +import io.github.z4kn4fein.semver.inc + +/** + * Should calculate the next version based on the current branch name + */ +class BranchVersionCalculator( + private val kGit: KGit, +) : VersionCalculator { + override fun calculate( + latestVersion: Version, + context: VersionCalculatorContext, + ): String = + with(context) { + val currentBranch = kGit.branch.currentRef(forTesting) + val developmentBranch = kGit.branches.findDevelopmentBranch(developmentBranch, mainBranch) + val mainBranch = kGit.branches.findMainBranch(mainBranch) + + val commitCount = + if (currentBranch != developmentBranch) { + kGit.branches.commitCountBetween(developmentBranch.name, currentBranch.name) + } else { + kGit.branches.commitCountBetween(mainBranch.name, currentBranch.name) + } + + val prereleaseLabel = currentBranch.sanitizedWithoutPrefix() + val prereleaseLabelWithCommitCount = "$prereleaseLabel.$commitCount" + + return latestVersion + .inc(modifier.toInc(), prereleaseLabelWithCommitCount) + .appendBuildMetadata(context.appendBuildMetadata) + .toString() + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/GitStateVersionCalculator.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/GitStateVersionCalculator.kt new file mode 100644 index 0000000..c2322a7 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/GitStateVersionCalculator.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import io.github.z4kn4fein.semver.Version +import io.github.z4kn4fein.semver.nextPreRelease + +object GitStateVersionCalculator : VersionCalculator { + override fun calculate( + latestVersion: Version, + context: VersionCalculatorContext, + ): String = + with(context) { + return latestVersion.nextPreRelease(gitState.description).toString() + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/StageVersionCalculator.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/StageVersionCalculator.kt new file mode 100644 index 0000000..34a8d95 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/StageVersionCalculator.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import com.figure.gradle.semver.internal.extensions.appendBuildMetadata +import com.figure.gradle.semver.internal.extensions.nextVersion +import io.github.z4kn4fein.semver.Version + +/** + * Calculates the next version based on the stage and modifier. + */ +object StageVersionCalculator : VersionCalculator { + override fun calculate( + latestVersion: Version, + context: VersionCalculatorContext, + ): String = + with(context) { + return latestVersion + .nextVersion(stage, modifier) + .appendBuildMetadata(context.appendBuildMetadata) + .toString() + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculator.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculator.kt new file mode 100644 index 0000000..8ffdda6 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculator.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import io.github.z4kn4fein.semver.Version + +interface VersionCalculator { + fun calculate( + latestVersion: Version, + context: VersionCalculatorContext, + ): String +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculatorContext.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculatorContext.kt new file mode 100644 index 0000000..f01d7a0 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionCalculatorContext.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import com.figure.gradle.semver.internal.command.GitState +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.Stage + +data class VersionCalculatorContext( + val stage: Stage, + val modifier: Modifier, + val forTesting: Boolean, + val gitState: GitState, + val mainBranch: String? = null, + val developmentBranch: String? = null, + val appendBuildMetadata: BuildMetadataOptions, +) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactory.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactory.kt new file mode 100644 index 0000000..2a6c15b --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactory.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import com.figure.gradle.semver.internal.command.GitState +import com.figure.gradle.semver.internal.command.KGit +import com.figure.gradle.semver.internal.errors.InvalidOverrideVersionError +import com.figure.gradle.semver.internal.logging.warn +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.Stage +import io.github.z4kn4fein.semver.nextPatch +import io.github.z4kn4fein.semver.toVersion +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters + +private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) + +fun ProviderFactory.versionFactory(context: VersionFactoryContext): Provider = + of(VersionFactory::class.java) { spec -> + spec.parameters { + it.versionFactoryContext.set(context) + } + } + +abstract class VersionFactory : ValueSource { + interface Params : ValueSourceParameters { + val versionFactoryContext: Property + } + + private fun Params.toVersionCalculatorContext(gitState: GitState) = + with(versionFactoryContext.get()) { + VersionCalculatorContext( + stage = stage, + modifier = modifier, + forTesting = forTesting, + gitState = gitState, + mainBranch = mainBranch, + developmentBranch = developmentBranch, + appendBuildMetadata = appendBuildMetadata, + ) + } + + override fun obtain(): String { + val factoryContext = parameters.versionFactoryContext.get() + + if (!factoryContext.rootDir.resolve(".git").exists()) { + log.warn { "Git is not initialized in this repository. Please run 'git init' to initialize it." } + log.warn { "Alternatively, for composite projects, specify the `rootProjectDir` in the semver configuration block." } + val nextVersion = factoryContext.initialVersion.toVersion().nextPatch().toString() + return "$nextVersion-UNINITIALIZED-REPO" + } + + if (factoryContext.modifier == Modifier.Major && factoryContext.forMajorVersion != null) { + error("forMajorVersion cannot be used with the 'major' modifier") + } + + val kgit = KGit(directory = factoryContext.rootDir) + + val context = parameters.toVersionCalculatorContext(kgit.state()) + + val overrideVersion = factoryContext.overrideVersion + val latestVersion = kgit.tags.latestOrInitial(factoryContext.initialVersion, factoryContext.forMajorVersion) + val latestNonPreReleaseVersion = kgit.tags.latestNonPreReleaseOrInitial(factoryContext.initialVersion) + + val version = + when { + context.gitState != GitState.NOMINAL -> { + GitStateVersionCalculator.calculate(latestNonPreReleaseVersion, context) + } + + overrideVersion != null -> { + runCatching { + overrideVersion.toVersion() + }.getOrElse { + throw InvalidOverrideVersionError(overrideVersion) + }.toString() + } + + kgit.branch.isOnMainBranch(context.mainBranch, context.forTesting) -> { + StageVersionCalculator.calculate(latestVersion, context) + } + + // Works for any branch + else -> { + // Compute based on the branch name, otherwise, use the stage to compute the next version + if (context.stage == Stage.Auto) { + BranchVersionCalculator(kgit).calculate(latestNonPreReleaseVersion, context) + } else { + StageVersionCalculator.calculate(latestVersion, context) + } + } + } + + return version + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactoryContext.kt b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactoryContext.kt new file mode 100644 index 0000000..fd4ba36 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/calculator/VersionFactoryContext.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.calculator + +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.Stage +import java.io.File +import java.io.Serializable + +data class VersionFactoryContext( + val initialVersion: String, + val stage: Stage, + val modifier: Modifier, + val forTesting: Boolean, + val overrideVersion: String?, + val forMajorVersion: Int?, + val rootDir: File, + val mainBranch: String?, + val developmentBranch: String?, + val appendBuildMetadata: BuildMetadataOptions, +) : Serializable { + companion object { + private const val serialVersionUID: Long = 1L + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Add.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Add.kt new file mode 100644 index 0000000..d126b07 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Add.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.dircache.DirCache + +class Add( + private val git: Git, +) { + operator fun invoke(filename: String): DirCache? = git.add().addFilepattern(filename).call() + + fun all(): DirCache? = git.add().addFilepattern(".").call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Branch.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Branch.kt new file mode 100644 index 0000000..4bebbea --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Branch.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import com.figure.gradle.semver.internal.command.extension.revWalk +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.lib.Ref +import org.eclipse.jgit.revwalk.RevCommit + +class Branch( + private val git: Git, + private val branchList: BranchList, +) { + private val githubHeadRef: String = "GITHUB_HEAD_REF" + + private val shortName: String + get() = git.repository.branch + + val fullName: String + get() = git.repository.fullBranch + + private val branchRef: Ref + get() = git.repository.findRef(shortName) + + val headRef: Ref? + get() = git.repository.exactRef(Constants.HEAD) + + val headCommit: RevCommit + get() = git.revWalk { it.parseCommit(branchRef.objectId) } + + fun currentRef(forTesting: Boolean = false): Ref = + if (forTesting) { + branchRef + } else { + git.repository.findRef(System.getenv(githubHeadRef) ?: shortName) + } + + fun isOnMainBranch( + providedMainBranch: String? = null, + forTesting: Boolean = false, + ): Boolean = currentRef(forTesting).name == branchList.findMainBranch(providedMainBranch).name + + fun create(branchName: String): Ref = + git.branchCreate() + .setName(branchName) + .call() + + fun delete(vararg branchNames: String): List = + git.branchDelete() + .setBranchNames(*branchNames) + .setForce(true) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/BranchList.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/BranchList.kt new file mode 100644 index 0000000..dac2b1c --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/BranchList.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import com.figure.gradle.semver.internal.command.extension.revWalk +import com.figure.gradle.semver.internal.extensions.R_REMOTES_ORIGIN +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.api.ListBranchCommand +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.lib.ObjectId +import org.eclipse.jgit.lib.Ref + +class BranchList( + private val git: Git, +) { + fun findDevelopmentBranch( + providedDevelopmentBranch: String?, + providedMainBranch: String?, + ): Ref = + find(providedDevelopmentBranch) + ?: find("develop") + ?: find("devel") + ?: find("dev") + ?: find(providedMainBranch) // Need to fall back in cases where the main branch is not main or master + ?: find("main") + ?: find("master") + ?: error( + buildString { + append("Could not determine default branch. ") + append("Searched, in order, for: ") + append("$providedDevelopmentBranch, develop, devel, dev, $providedMainBranch, main, master") + }, + ) + + fun findMainBranch(providedMainBranch: String?): Ref = + find(providedMainBranch) + ?: find("main") + ?: find("master") + ?: error("Could not determine main branch. Searched, in order, for: $providedMainBranch, main, master") + + fun exists(branchName: String): Boolean = find(branchName) != null + + /** + * Finds an exact branch by name preferring local branches over remote branches, but will return + * remote branches if the local branch does not exist. + */ + private fun find(branchName: String?): Ref? = + branchName + ?.takeIf { it.isNotBlank() } + ?.let { nonBlankBranchName -> + findAll(nonBlankBranchName).let { matchingBranches -> + matchingBranches.find { Constants.R_HEADS in it.name } + ?: matchingBranches.find { Constants.R_REMOTES in it.name } + } + } + + /** + * Find all branches given the branch name. Can be full or short name. + */ + private fun findAll(branchName: String): List = + git.branchList() + .setListMode(ListBranchCommand.ListMode.ALL) + .call() + .filter { branchName.lowercase() in it.name.lowercase() } + + fun commitCountBetween( + baseBranchName: String, + targetBranchName: String, + ): Int { + // Try to resolve the remote branch first, then fall back to the local branch + // This should fix situations where you're on the base branch with commits locally. + // If that's the case, you'll likely get 0 commits between the base and the target branch. + val baseBranch: ObjectId = + git.repository.resolve("$R_REMOTES_ORIGIN/$baseBranchName") + ?: git.repository.resolve(baseBranchName) + + val targetBranch: ObjectId = git.repository.resolve(targetBranchName) + + return git.revWalk { revWalk -> + revWalk.apply { + markStart(parseCommit(targetBranch)) + markUninteresting(parseCommit(baseBranch)) + } + + revWalk.toList().size + } + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Checkout.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Checkout.kt new file mode 100644 index 0000000..6691ee3 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Checkout.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref + +class Checkout( + private val git: Git, +) { + operator fun invoke( + branchName: String, + createBranch: Boolean = false, + ): Ref = + git.checkout() + .setName(branchName) + .setCreateBranch(createBranch) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Commit.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Commit.kt new file mode 100644 index 0000000..ea9574f --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Commit.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.revwalk.RevCommit + +class Commit( + private val git: Git, +) { + operator fun invoke( + message: String, + allowEmptyCommit: Boolean = false, + ): RevCommit = + git.commit() + .setMessage(message) + .setAllowEmpty(allowEmptyCommit) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Config.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Config.kt new file mode 100644 index 0000000..02ca879 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Config.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.ConfigConstants + +class Config( + private val git: Git, +) { + fun author( + name: String, + email: String, + ) { + val config = git.repository.config + + config.setString( + ConfigConstants.CONFIG_USER_SECTION, + null, + ConfigConstants.CONFIG_KEY_NAME, + name, + ) + config.setString( + ConfigConstants.CONFIG_USER_SECTION, + null, + ConfigConstants.CONFIG_KEY_EMAIL, + email, + ) + + config.save() + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Init.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Init.kt new file mode 100644 index 0000000..9fe83c4 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Init.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import java.io.File + +object Init { + operator fun invoke( + directory: File, + bare: Boolean = false, + initialBranch: String = "main", + ): Git = + Git.init() + .setDirectory(directory) + .setBare(bare) + .setInitialBranch(initialBranch) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/KGit.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/KGit.kt new file mode 100644 index 0000000..296f06c --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/KGit.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import java.io.File + +class KGit( + directory: File? = null, + initializeRepo: InitializeRepo? = null, +) { + val git: Git by lazy { + when { + directory != null && initializeRepo != null -> + init( + directory, + bare = initializeRepo.bare, + initialBranch = initializeRepo.initialBranch, + ) + + directory != null -> open(directory) + else -> open() + } + } + + companion object { + val init = Init + val open = Open + } + + val add = Add(git) + val checkout = Checkout(git) + val config = Config(git) + val branches = BranchList(git) + val branch = Branch(git, branches) + val commit = Commit(git) + val log = Log(git) + val print = Print(this) + val push = Push(git) + val remote = Remote(git) + val state = State(git) + val tag = Tag(git) + val tags = TagList(git) + + fun close() = git.close() +} + +data class InitializeRepo( + val bare: Boolean, + val initialBranch: String, +) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Log.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Log.kt new file mode 100644 index 0000000..f40d7e4 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Log.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.revwalk.RevCommit + +class Log( + private val git: Git, +) { + operator fun invoke(): List = git.log().call().toList() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Open.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Open.kt new file mode 100644 index 0000000..1491423 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Open.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.storage.file.FileRepositoryBuilder +import java.io.File + +object Open { + operator fun invoke(): Git = + Git( + FileRepositoryBuilder() + .readEnvironment() + .findGitDir() + .build(), + ) + + operator fun invoke(rootDir: File): Git = Git.open(rootDir) +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Print.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Print.kt new file mode 100644 index 0000000..f66aa3e --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Print.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter + +private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) +private const val SHORTENED_COMMIT_LENGTH = 7 + +class Print( + private val kgit: KGit, +) { + fun tags(debug: Boolean) { + val logLevel = if (debug) LogLevel.LIFECYCLE else LogLevel.INFO + log.log(logLevel, "Tags:") + kgit.tags().forEach { tag -> + log.log(logLevel, " ${tag.name}") + } + } + + fun refs(debug: Boolean) { + val logLevel = if (debug) LogLevel.LIFECYCLE else LogLevel.INFO + log.log(logLevel, "Refs:") + kgit.git.repository.refDatabase.refs.forEach { ref -> + log.log(logLevel, " ${ref.name}") + } + } + + fun commits(debug: Boolean) { + val logLevel = if (debug) LogLevel.LIFECYCLE else LogLevel.INFO + log.log(logLevel, "Commits:") + kgit.log().forEach { commit -> + log.log( + logLevel, + " ${commit.name.take(SHORTENED_COMMIT_LENGTH)} ${convertEpochToCustomFormat(commit.commitTime)} ${commit.shortMessage}", + ) + } + } + + private fun convertEpochToCustomFormat(epochTime: Int): String { + val instant = Instant.ofEpochSecond(epochTime.toLong()) + val formatter = DateTimeFormatter.ofPattern("MM-dd-yy HH:mm:ss").withZone(ZoneId.systemDefault()) + return formatter.format(instant) + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Push.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Push.kt new file mode 100644 index 0000000..6fc21d0 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Push.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.transport.PushResult +import org.eclipse.jgit.transport.RefSpec + +class Push( + private val git: Git, +) { + operator fun invoke(): MutableIterable? = git.push().call() + + fun branch(branch: String): MutableIterable? = + git.push() + .setRefSpecs(RefSpec("${Constants.R_HEADS}$branch:${Constants.R_HEADS}$branch")) + .call() + + fun tag(tag: String): MutableIterable? = + git.push() + .setRefSpecs(RefSpec("${Constants.R_TAGS}$tag:${Constants.R_TAGS}$$tag")) + .call() + + fun allBranches(): MutableIterable? = + git.push() + .setPushAll() // Push all branches under refs/heads/* + .call() + + fun allTags(): MutableIterable? = + git.push() + .setPushTags() // Push all tags under refs/tags/* + .call() + + fun all(): MutableIterable? = + git.push() + .setPushAll() // Push all branches under refs/heads/* + .setPushTags() // Push all tags under refs/tags/* + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Remote.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Remote.kt new file mode 100644 index 0000000..59b7d75 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Remote.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.transport.RemoteConfig +import org.eclipse.jgit.transport.URIish +import java.net.URL + +class Remote( + private val git: Git, +) { + fun add( + remoteGit: Git, + remoteName: String = Constants.DEFAULT_REMOTE_NAME, + ): RemoteConfig = + git.remoteAdd() + .setName(remoteName) + .setUri(URIish(remoteGit.repository.directory.toURI().toURL())) + .call() + + fun add( + remoteUri: String, + remoteName: String = Constants.DEFAULT_REMOTE_NAME, + ): RemoteConfig = + git.remoteAdd() + .setName(remoteName) + .setUri(URIish(remoteUri)) + .call() + + fun add( + remoteUri: URL, + remoteName: String = Constants.DEFAULT_REMOTE_NAME, + ): RemoteConfig = + git.remoteAdd() + .setName(remoteName) + .setUri(URIish(remoteUri)) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/State.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/State.kt new file mode 100644 index 0000000..b454c79 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/State.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants + +// This is heavily influenced by the gitstatus logic found here: +// https://github.com/magicmonty/bash-git-prompt/ +// TODO: Future consideration: Support rebasing step in the version. So something like: +// - `1.2.3-rebasing.2-12` - would mean step 2 out of 12 of rebasing. +class State( + private val git: Git, +) { + operator fun invoke(): GitState = + when { + rebasing -> GitState.REBASING + merging -> GitState.MERGING + cherryPicking -> GitState.CHERRY_PICKING + reverting -> GitState.REVERTING + bisecting -> GitState.BISECTING + detachedHead -> GitState.DETACHED_HEAD + else -> GitState.NOMINAL + } + + private val rebasing: Boolean + get() = git.repository.directory.resolve("rebase-merge").exists() + + private val merging: Boolean + get() = git.repository.directory.resolve("MERGE_HEAD").exists() + + private val cherryPicking: Boolean + get() = git.repository.directory.resolve("CHERRY_PICK_HEAD").exists() + + private val reverting: Boolean + get() = git.repository.directory.resolve("REVERT_HEAD").exists() + + private val bisecting: Boolean + get() = git.repository.directory.resolve("BISECT_LOG").exists() + + private val detachedHead: Boolean + get() = git.repository.exactRef(Constants.HEAD).target.objectId.name == git.repository.branch +} + +// TODO: Add state for uninitialized repo and gently nag on every build +enum class GitState(val description: String) { + NOMINAL(""), + BISECTING("BISECTING"), + CHERRY_PICKING("CHERRY-PICKING"), + DETACHED_HEAD("DETACHED-HEAD"), + MERGING("MERGING"), + REBASING("REBASING"), + REVERTING("REVERTING"), +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/Tag.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/Tag.kt new file mode 100644 index 0000000..a64c94a --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/Tag.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Ref + +class Tag( + private val git: Git, +) { + operator fun invoke(tagName: String): Ref? = + git.tag() + .setName(tagName) + .call() + + fun delete(vararg tag: String): MutableList? = + git.tagDelete() + .setTags(*tag) + .call() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/TagList.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/TagList.kt new file mode 100644 index 0000000..9696615 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/TagList.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command + +import com.figure.gradle.semver.internal.extensions.isNotPreRelease +import com.figure.gradle.semver.internal.properties.Stage +import io.github.z4kn4fein.semver.Version +import io.github.z4kn4fein.semver.toVersion +import io.github.z4kn4fein.semver.toVersionOrNull +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Constants +import org.eclipse.jgit.lib.Ref + +class TagList( + private val git: Git, +) { + operator fun invoke(): List = git.tagList().call() + + fun find(tagName: String): Ref? = invoke().find { it.name == tagName } + + val versionedTags: List + get() = invoke().mapNotNull { it.name.replace(Constants.R_TAGS, "").toVersionOrNull(strict = false) } + + private fun latest(forMajorVersion: Int?): Version? { + val stages = Stage.entries.map { stage -> stage.value.lowercase() } + + return versionedTags + // Get only stable and staged pre-releases + .filter { version -> + val prereleaseLabel = version.preRelease?.substringBefore(".")?.lowercase() + version.isNotPreRelease || prereleaseLabel in stages + } + .let { versions -> + if (forMajorVersion != null) { + versions.filter { version -> version.major == forMajorVersion } + } else { + versions + } + } + .maxOrNull() + } + + fun latestOrInitial( + initial: String, + forMajorVersion: Int?, + ): Version = latest(forMajorVersion) ?: initial.toVersion() + + private val latestNonPreRelease: Version? + get() = + versionedTags + .filter { version -> version.isNotPreRelease } + .maxOrNull() + + fun latestNonPreReleaseOrInitial(initial: String): Version = latestNonPreRelease ?: initial.toVersion() +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/command/extension/RevWalkExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/command/extension/RevWalkExtensions.kt new file mode 100644 index 0000000..7968ab8 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/command/extension/RevWalkExtensions.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.command.extension + +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.lib.Repository +import org.eclipse.jgit.revwalk.RevWalk + +fun Repository.revWalk(action: (RevWalk) -> R) = RevWalk(this).use(action) + +fun Git.revWalk(action: (RevWalk) -> R) = repository.revWalk(action) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/errors/InvalidOverrideVersionError.kt b/src/main/kotlin/com/figure/gradle/semver/internal/errors/InvalidOverrideVersionError.kt new file mode 100644 index 0000000..6eb8745 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/errors/InvalidOverrideVersionError.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.errors + +class InvalidOverrideVersionError( + invalidVersion: String, +) : Exception( + "Invalid override version provided: $invalidVersion", + ) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/exceptions/SemverExceptions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/exceptions/SemverExceptions.kt deleted file mode 100644 index 5fb3041..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/exceptions/SemverExceptions.kt +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.exceptions - -import com.figure.gradle.semver.internal.git.GitRef - -internal class GitException(t: Throwable) : - Exception(t) - -internal class UnexpectedException(message: String) : - Exception("Something unexpected occurred: $message") - -internal class MissingBranchMatchingConfigurationException(currentBranch: GitRef.Branch) : - Exception("Missing branch matching configuration for current branch: ${currentBranch.name}") - -internal class TagAlreadyExistsException(tag: String) : - Exception( - """ - |Tag $tag already exists on remote! Either skip publishing the artifact on the next run or delete - | the existing tag before running again. - """.trimMargin().replace("\n", ""), - ) - -internal class UnsupportedBranchingStrategy() : - Exception( - "Unsupported branching strategy. Supported branching strategies: main, master, main-develop, master-develop", - ) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/extensions/FileExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/FileExtensions.kt new file mode 100644 index 0000000..51f059c --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/FileExtensions.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.extensions + +import java.io.File + +fun File.getOrCreate(): File { + if (!exists()) { + parentFile?.mkdirs() + createNewFile() + } + return this +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/extensions/PluginAwareExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/PluginAwareExtensions.kt new file mode 100644 index 0000000..dce2322 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/PluginAwareExtensions.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.extensions + +import org.gradle.api.Project +import org.gradle.api.flow.FlowScope +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import org.gradle.api.plugins.ExtensionContainer +import org.gradle.api.plugins.PluginAware +import org.gradle.api.provider.ProviderFactory +import org.gradle.kotlin.dsl.support.serviceOf +import java.io.File + +val PluginAware.providers: ProviderFactory + get() = + when (this) { + is Settings -> providers + is Project -> providers + else -> error("Not a project or settings") + } + +val PluginAware.rootDir: File + get() = + when (this) { + is Settings -> settingsDir + is Project -> rootDir + else -> error("Not a project or settings") + } + +val PluginAware.projectDir: File + get() = + when (this) { + is Settings -> settingsDir + is Project -> projectDir + else -> error("Not a project or settings") + } + +val PluginAware.gradle: Gradle + get() = + when (this) { + is Settings -> gradle + is Project -> gradle + else -> error("Not a project or settings") + } + +val PluginAware.extensions: ExtensionContainer + get() = + when (this) { + is Settings -> extensions + is Project -> extensions + else -> error("Not a project or settings") + } + +val PluginAware.flowScope: FlowScope + get() = + when (this) { + is Project -> serviceOf() + is Settings -> serviceOf() + else -> error("Not a project or settings") + } diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/extensions/RefExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/RefExtensions.kt new file mode 100644 index 0000000..396f498 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/RefExtensions.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.extensions + +import org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME +import org.eclipse.jgit.lib.Constants.R_HEADS +import org.eclipse.jgit.lib.Constants.R_REMOTES +import org.eclipse.jgit.lib.Ref + +const val R_REMOTES_ORIGIN = "$R_REMOTES$DEFAULT_REMOTE_NAME" + +private val validCharacters: Regex = """[^0-9A-Za-z\-_.]+""".toRegex() + +fun Ref.sanitizedWithoutPrefix(): String = + name.trim() + .lowercase() + .replace(R_HEADS, "") + .replace("$R_REMOTES_ORIGIN/", "") + .removePrefix("/") + .replace(validCharacters, "-") diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtension.kt b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtension.kt new file mode 100644 index 0000000..6e1190b --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtension.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.extensions + +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions.ALWAYS +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions.LOCALLY +import com.figure.gradle.semver.internal.properties.BuildMetadataOptions.NEVER +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.Stage +import io.github.z4kn4fein.semver.Inc +import io.github.z4kn4fein.semver.Version +import io.github.z4kn4fein.semver.inc +import io.github.z4kn4fein.semver.nextPatch +import io.github.z4kn4fein.semver.nextPreRelease +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +fun Version.nextVersion( + providedStage: Stage, + providedModifier: Modifier, +): Version = + when { + isInvalidVersionForComputation() -> { + error( + "Cannot compute next version because the latest computed version likely contains branch " + + "information: $this. If you see this error, please file an issue. This is a bug.", + ) + } + + // next snapshot + (providedStage == Stage.Snapshot) -> { + nextSnapshot(providedModifier.toInc()) + } + + // next pre-release + (providedModifier == Modifier.Auto && this.isPreRelease) && + (providedStage == Stage.Auto || providedStage == this.stage) -> { + nextPreRelease() + } + + // next stable + (providedStage == Stage.Auto && this.isNotPreRelease) || providedStage == Stage.Stable -> { + nextStable(providedModifier.toInc()) + } + + // next stable with next pre-release identifier + providedStage == Stage.Auto && this.isPreRelease -> { + nextStableWithPreRelease(providedModifier.toInc(), this.preRelease) + } + + // next stable with new pre-release identifier + providedModifier != Modifier.Auto -> { + newPreRelease(providedModifier.toInc(), providedStage) + } + + // next patch with new pre-release identifier + else -> { + nextPatch("${providedStage.value}.1") + } + } + +fun Version.appendBuildMetadata(buildMetadataOptions: BuildMetadataOptions): Version { + val calculatedBuildMetadata = LocalDateTime.now().toBuildMetadata() + val withBuildMetadata = Version(major, minor, patch, preRelease, calculatedBuildMetadata) + return when (buildMetadataOptions) { + ALWAYS -> withBuildMetadata + NEVER -> this + LOCALLY -> withBuildMetadata.takeIf { System.getenv("CI") == null } ?: this + } +} + +private fun LocalDateTime.toBuildMetadata(): String { + val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm") + return this.format(formatter) +} + +private fun Version.nextStable(incrementer: Inc): Version = inc(incrementer) + +private fun Version.nextStableWithPreRelease( + incrementer: Inc, + preRelease: String?, +): Version = inc(incrementer, preRelease) + +private fun Version.nextSnapshot(incrementer: Inc): Version = inc(incrementer, Stage.Snapshot.value) + +private fun Version.newPreRelease( + incrementer: Inc, + stage: Stage, +): Version = inc(incrementer, "${stage.value}.1") + +private val Version.stage: Stage? + get() = Stage.entries.find { preRelease?.contains(it.value, ignoreCase = true) == true } + +/** + * This is different from being stable or not. + * + * "stable" means that the major version is greater than 0 AND does not have a pre-release identifier. + * + * This just means that the version lacks a pre-release identifier. + */ +val Version.isNotPreRelease: Boolean + get() = !isPreRelease + +/** + * Current version is invalid for computation when: + * - A pre-release + * - The pre-release label is not a valid stage (aka the version has a branch-based pre-release label) + */ +private fun Version.isInvalidVersionForComputation(): Boolean { + val stages = Stage.entries.map { it.value.lowercase() } + val prereleaseLabel = preRelease?.substringBefore(".")?.lowercase() + + return isPreRelease && prereleaseLabel !in stages +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/git/Git.kt b/src/main/kotlin/com/figure/gradle/semver/internal/git/Git.kt deleted file mode 100644 index ae6e430..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/git/Git.kt +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.git - -import com.figure.gradle.semver.internal.exceptions.GitException -import com.figure.gradle.semver.internal.exceptions.UnexpectedException -import com.figure.gradle.semver.internal.githubActionsBuild -import com.figure.gradle.semver.internal.pullRequestEvent -import com.figure.gradle.semver.internal.pullRequestHeadRef -import com.figure.gradle.semver.internal.semverError -import com.figure.gradle.semver.internal.semverInfo -import com.figure.gradle.semver.internal.semverWarn -import net.swiftzer.semver.SemVer -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.api.ListBranchCommand -import org.eclipse.jgit.lib.ObjectId -import org.eclipse.jgit.lib.Ref -import org.eclipse.jgit.revwalk.RevCommit -import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.storage.file.FileRepositoryBuilder -import org.gradle.api.Project -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging -import java.io.File - -private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) - -internal fun openGitDir(gitDir: String) = - Git( - FileRepositoryBuilder() - .setGitDir(File(gitDir)) - .readEnvironment() - .findGitDir() - .build(), - ) - -internal fun Project.git(gitDir: String): Git = - Git( - FileRepositoryBuilder() - .setGitDir(file(gitDir)) - .readEnvironment() - .findGitDir() - .build(), - ) - -internal fun Git.tagMap(prefix: String): Map { - return tagList().call().toList() - .mapNotNull { ref -> - ref.semverTag(prefix)?.let { semver -> - (repository.refDatabase.peel(ref).peeledObjectId ?: ref.objectId) to semver - } - }.toMap() -} - -private fun Ref?.semverTag(prefix: String): SemVer? { - return this?.name?.semverTag(prefix) -} - -internal fun String?.semverTag(prefix: String): SemVer? = - this?.substringAfterLast("/$prefix")?.let { semver -> - if (semver.isNotBlank() && semver.count { letter -> letter == '.' } == 2) { - runCatching { SemVer.parse(semver) }.getOrNull() - } else { - null - } - } - -internal fun Git.currentBranchRef(): String? = - if (githubActionsBuild() && pullRequestEvent()) { - pullRequestHeadRef()?.let { ref -> "${GitRef.REMOTE_ORIGIN}/$ref" } - } else { - repository.fullBranch - } - -internal fun String?.shortName(): Result { - return this?.let { - when { - it.startsWith(GitRef.REF_HEAD) -> parseBranchName(it, GitRef.REF_HEAD) - it.startsWith(GitRef.REMOTE_ORIGIN) -> parseBranchName(it, GitRef.REMOTE_ORIGIN) - else -> Result.failure(UnexpectedException("Unable to parse branch ref: $it")) - } - } ?: Result.failure(UnexpectedException("Unable to parse null branch ref")) -} - -private fun parseBranchName( - fullBranchName: String, - prefix: String, -): Result { - return runCatching { - Result.success(fullBranchName.substringAfter("$prefix/")) - }.getOrElse { ex -> - Result.failure(GitException(ex)) - } -} - -internal fun Git.calculateBaseBranchVersion( - targetBranch: GitRef.Branch, - currentBranch: GitRef.Branch, - tags: Map, -): Result { - return latestCommitOnBranch(currentBranch).map { head -> - findYoungestTagOnBranchOlderThanTarget(targetBranch, head, tags) - } -} - -internal fun Git.latestCommitOnBranch(branch: GitRef.Branch): Result = - runCatching { - val walk = RevWalk(repository) - val branchRef = repository.findRef(branch.refName) - val latestCommit = walk.parseCommit(branchRef.objectId) - walk.dispose() - Result.success(latestCommit) - }.getOrElse { t -> - Result.failure(GitException(t)) - } - -private fun Git.findYoungestTagOnBranchOlderThanTarget( - branch: GitRef.Branch, - target: RevCommit, - tags: Map, -): SemVer? { - val branchRef = repository.exactRef(branch.refName) - if (branchRef == null) { - log.semverError("Failed to find exact git ref for branch: $branch, aborting build. Check that the full git history is available.") - } else { - log.semverInfo("Pulling log for $branch refName, exactRef: $branchRef, target: $target") - } - - return log().add(branchRef.objectId).call() - .firstOrNull { it.commitTime <= target.commitTime && tags.containsKey(it.toObjectId()) } - ?.let { tags[it.id] } -} - -internal fun gitCommitsSinceBranchPoint( - git: Git, - branchPoint: RevCommit, - branch: GitRef.Branch, - tags: Map, -): Result { - val commits = git.log().call().toList() - val newCommits = - commits.takeWhile { - it.toObjectId() != branchPoint.toObjectId() && it.commitTime > branchPoint.commitTime - } - - return when { - newCommits.map { it.toObjectId() }.contains(branchPoint.toObjectId()) -> { - Result.success(newCommits.size) - } - - newCommits.size != commits.size -> { - log.semverInfo( - buildString { - append("Unable to find branch point [${branchPoint.id.name}: ${branchPoint.shortMessage}] ") - append("typically this happens when commits were squashed & merged and this branch [$branch] has ") - append("not been rebased yet, using nearest commit with a semver tag, this is just an estimate") - }, - ) - - git.findYoungestTagCommitOnBranch(branch, tags) - ?.let { youngestTag -> - log.semverInfo("Youngest tag on this branch is at ${youngestTag.id.name} => ${tags[youngestTag.id]}") - Result.success(commits.takeWhile { it.id != youngestTag.id }.size) - } - ?: run { - log.semverWarn( - buildString { - append("Failed to find any semver tags on branch [$branch], does main have ") - append("any version tags? Using 0 as commit count since branch point") - }, - ) - Result.success(0) - } - } - - else -> { - Result.failure( - UnexpectedException( - buildString { - append("The branch ${branch.refName} did not contain the branch point ") - append("[${branchPoint.toObjectId()}: ${branchPoint.shortMessage}], ") - append("have you rebased your current branch?") - }, - ), - ) - } - } -} - -private fun Git.findYoungestTagCommitOnBranch( - branch: GitRef.Branch, - tags: Map, -): RevCommit? { - val branchRef = repository.exactRef(branch.refName) - if (branchRef == null) { - log.semverError("Failed to find exact git ref for branch: $branch, aborting build. Check that the full git history is available.") - } else { - log.semverInfo("Pulling log for $branch refName, exactRef: $branchRef") - } - return log().add(branchRef.objectId).call() - .firstOrNull { tags.containsKey(it.toObjectId()) } -} - -fun Git.hasBranch(branch: GitRef.Branch): Boolean = - runCatching { - val branchNames = - branchList() - .setListMode(ListBranchCommand.ListMode.ALL) - .call() - .mapNotNull { it.name.shortName().getOrNull() } - - branch.name in branchNames - }.getOrElse { - false - } diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/git/GitRef.kt b/src/main/kotlin/com/figure/gradle/semver/internal/git/GitRef.kt deleted file mode 100644 index 53b7aba..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/git/GitRef.kt +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.git - -import org.eclipse.jgit.lib.Constants - -sealed interface GitRef { - companion object { - val REF_HEAD = Constants.R_HEADS.substringBeforeLast("/") - val REF_REMOTE = Constants.R_REMOTES.substringBeforeLast("/") - val REMOTE_ORIGIN = "$REF_REMOTE/${Constants.DEFAULT_REMOTE_NAME}" - val VALID_CHARACTERS = """[^0-9A-Za-z\-_.]+""".toRegex() - } - - data class Branch( - // example: `main` - val name: String, - // example: `refs/remotes/origin/main` - val refName: String, - ) { - constructor(name: String) : this(name, "$REF_HEAD/$name") - - companion object { - val MAIN = Branch("main", "$REMOTE_ORIGIN/main") - val MASTER = Branch("master", "$REMOTE_ORIGIN/master") - val DEVELOP = Branch("develop", "$REMOTE_ORIGIN/develop") - } - - fun sanitizedNameWithoutPrefix(): String = - name.trim() - .lowercase() - .replaceBefore("/", "") - .removePrefix("/") - .replace(VALID_CHARACTERS, "-") - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/logging/LogExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/logging/LogExtensions.kt new file mode 100644 index 0000000..6c669eb --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/logging/LogExtensions.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.logging + +import org.gradle.api.logging.Logger + +private const val LOG_ERROR_PREFIX = "> ERROR " +private const val LOG_QUIET_PREFIX = "QUIET " +private const val LOG_WARN_PREFIX = "> WARN " +private const val LOG_LIFECYCLE_PREFIX = "> Version: " +private const val LOG_INFO_PREFIX = "INFO " +private const val LOG_DEBUG_PREFIX = "DEBUG " + +private fun String.colored(c: String) = "$c$this\u001B[0m" + +fun String.darkgray() = this.colored("\u001B[30m") + +fun String.red() = this.colored("\u001B[31m") + +fun String.green() = this.colored("\u001B[32m") + +fun String.yellow() = this.colored("\u001B[33m") + +fun String.purple() = this.colored("\u001B[35m") + +fun String.lightgray() = this.colored("\u001B[37m") + +fun String.bold() = this.colored("\u001B[1m") + +fun Logger.error(message: () -> String) = this.error(LOG_ERROR_PREFIX.bold() + message().red()) + +fun Logger.error( + throwable: Throwable?, + message: () -> String, +) = this.error(LOG_ERROR_PREFIX.bold() + message().red(), throwable) + +fun Logger.quiet(message: () -> String) = this.quiet(LOG_QUIET_PREFIX.bold() + message().lightgray()) + +fun Logger.warn(message: () -> String) = this.warn(LOG_WARN_PREFIX.bold() + message().yellow()) + +fun Logger.lifecycle(message: () -> String) = this.lifecycle(LOG_LIFECYCLE_PREFIX.bold() + message().purple()) + +fun Logger.info(message: () -> String) = this.info(LOG_INFO_PREFIX.bold() + message().green()) + +fun Logger.debug(message: () -> String) = this.debug(LOG_DEBUG_PREFIX.bold() + message().darkgray()) diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/logging/PostBuildVersionLogger.kt b/src/main/kotlin/com/figure/gradle/semver/internal/logging/PostBuildVersionLogger.kt new file mode 100644 index 0000000..09381b5 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/logging/PostBuildVersionLogger.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.logging + +import com.figure.gradle.semver.internal.extensions.flowScope +import org.gradle.api.flow.FlowAction +import org.gradle.api.flow.FlowParameters +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.plugins.PluginAware +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.kotlin.dsl.always + +private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) + +fun PluginAware.registerPostBuildVersionLogMessage(message: String) { + flowScope.always(PostBuildVersionLogger::class) { action -> + action.parameters.message.set(message) + } +} + +private abstract class PostBuildVersionLogger : FlowAction { + interface Params : FlowParameters { + @get:Input + val message: Property + } + + override fun execute(parameters: Params) { + log.lifecycle { parameters.message.get() } + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/properties/BuildMetadataOptions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/properties/BuildMetadataOptions.kt new file mode 100644 index 0000000..904b337 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/properties/BuildMetadataOptions.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.properties + +enum class BuildMetadataOptions { + ALWAYS, + NEVER, + LOCALLY, + ; + + companion object { + fun from( + value: String, + default: BuildMetadataOptions, + ): BuildMetadataOptions = + BuildMetadataOptions.entries + .find { it.name.equals(value, ignoreCase = true) } + ?: default + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/properties/Modifier.kt b/src/main/kotlin/com/figure/gradle/semver/internal/properties/Modifier.kt new file mode 100644 index 0000000..4069cdd --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/properties/Modifier.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.properties + +import io.github.z4kn4fein.semver.Inc + +enum class Modifier(val value: String) { + Major("major"), + Minor("minor"), + Patch("patch"), + Auto("auto"), + ; + + fun toInc(): Inc = + when (this) { + Major -> Inc.MAJOR + Minor -> Inc.MINOR + Patch -> Inc.PATCH + Auto -> Inc.PATCH + } + + companion object { + fun fromValue(value: String): Modifier = + entries.find { it.value.lowercase() == value.lowercase() } + ?: error("Invalid modifier provided: $value. Valid values are: ${entries.joinToString { it.value }}") + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverProperty.kt b/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverProperty.kt new file mode 100644 index 0000000..5fc9bab --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverProperty.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.properties + +enum class SemverProperty(val property: String) { + Stage("semver.stage"), + Modifier("semver.modifier"), + TagPrefix("semver.tagPrefix"), + OverrideVersion("semver.overrideVersion"), + ForMajorVersion("semver.forMajorVersion"), + AppendBuildMetadata("semver.appendBuildMetadata"), + + ForTesting("semver.forTesting"), +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverPropertyExtensions.kt b/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverPropertyExtensions.kt new file mode 100644 index 0000000..863e052 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/properties/SemverPropertyExtensions.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.properties + +import com.figure.gradle.semver.internal.extensions.gradle +import com.figure.gradle.semver.internal.extensions.projectDir +import com.figure.gradle.semver.internal.extensions.providers +import org.gradle.api.plugins.PluginAware +import org.gradle.api.provider.Provider +import java.io.File +import java.util.Properties + +private const val GRADLE_PROPERTIES = "gradle.properties" + +val PluginAware.modifier: Provider + get() = semverProperty(SemverProperty.Modifier).map { Modifier.fromValue(it) }.orElse(Modifier.Auto) + +val PluginAware.stage: Provider + get() = semverProperty(SemverProperty.Stage).map { Stage.fromValue(it) }.orElse(Stage.Auto) + +val PluginAware.tagPrefix: Provider + get() = semverProperty(SemverProperty.TagPrefix).orElse("v") + +val PluginAware.overrideVersion: Provider + get() = semverProperty(SemverProperty.OverrideVersion) + +val PluginAware.forMajorVersion: Provider + get() = + semverProperty(SemverProperty.ForMajorVersion).map { + runCatching { + it.toInt() + }.getOrElse { + error("semver.forMajorVersion must be representative of a valid major version line (0, 1, 2, etc.)") + } + } + +val PluginAware.appendBuildMetadata: Provider + get() = semverProperty(SemverProperty.AppendBuildMetadata) + +val PluginAware.forTesting: Provider + get() = semverProperty(SemverProperty.ForTesting).map { it.toBoolean() }.orElse(false) + +private fun PluginAware.gradlePropertiesProperty( + semverProperty: SemverProperty, + propertiesDirectory: File, +): Provider { + return if (propertiesDirectory.resolve(GRADLE_PROPERTIES).exists()) { + providers.provider { + Properties().apply { + propertiesDirectory.resolve(GRADLE_PROPERTIES).inputStream().use { load(it) } + }.getProperty(semverProperty.property) + } + } else { + providers.provider { null } + } +} + +/** + * This will search for the semver property in the following order: + * 1. Start parameter. ie: `-Psemver.=value` via cli + * 2. `gradle.properties` in the gradle user home directory + * 3. `gradle.properties` in the gradle home directory + * 4. `gradle.properties` in the project directory + * + * This search order is different compared to the standard search order implemented by Gradle itself. + * Gradle typically searches by project directory first, then gradle user home, then gradle home. + * + * Why the difference? Since this is primarily a developer preferred property, it makes sense to prefer + * the gradle user home and gradle home directory, which are not checked into a repository, over the project + * directory. This way, developers can have their own personal settings that are not checked into the repository. + */ +private fun PluginAware.semverProperty(semverProperty: SemverProperty): Provider = + when { + gradle.startParameter.projectProperties[semverProperty.property] != null -> { + providers.provider { gradle.startParameter.projectProperties[semverProperty.property] } + } + + gradlePropertiesProperty(semverProperty, gradle.gradleUserHomeDir).isPresent -> { + gradlePropertiesProperty(semverProperty, gradle.gradleUserHomeDir) + } + + gradle.gradleHomeDir?.exists() == true -> { + // C'mon compiler, you can be smarter than this to be able to do the smart cast! + gradlePropertiesProperty(semverProperty, gradle.gradleHomeDir!!) + } + + gradlePropertiesProperty(semverProperty, projectDir).isPresent -> { + gradlePropertiesProperty(semverProperty, projectDir) + } + + else -> { + providers.provider { null } + } + } diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/properties/Stage.kt b/src/main/kotlin/com/figure/gradle/semver/internal/properties/Stage.kt new file mode 100644 index 0000000..54e980f --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/properties/Stage.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.properties + +// In order from lowest to highest priority +enum class Stage(val value: String) { + Dev("dev"), + Alpha("alpha"), + Beta("beta"), + ReleaseCandidate("rc"), + Snapshot("SNAPSHOT"), + Final("final"), + GA("ga"), + Release("release"), + Stable("stable"), + Auto("auto"), + ; + + companion object { + fun fromValue(value: String): Stage = + entries.find { it.value.lowercase() == value.lowercase() } + ?: error("Invalid stage provided: $value. Valid values are: ${entries.joinToString { it.value.lowercase() }}") + } +} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/semver/GitContextProviderOperations.kt b/src/main/kotlin/com/figure/gradle/semver/internal/semver/GitContextProviderOperations.kt deleted file mode 100644 index 2de12a9..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/semver/GitContextProviderOperations.kt +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.semver - -import com.figure.gradle.semver.external.ContextProviderOperations -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.internal.git.calculateBaseBranchVersion -import com.figure.gradle.semver.internal.git.currentBranchRef -import com.figure.gradle.semver.internal.git.gitCommitsSinceBranchPoint -import com.figure.gradle.semver.internal.git.latestCommitOnBranch -import com.figure.gradle.semver.internal.git.shortName -import com.figure.gradle.semver.internal.git.tagMap -import net.swiftzer.semver.SemVer -import org.eclipse.jgit.api.Git - -internal class GitContextProviderOperations( - private val git: Git, - config: VersionCalculatorConfig, -) : ContextProviderOperations { - private val tags = git.tagMap(config.tagPrefix) - - override fun currentBranch(): GitRef.Branch? { - return git.currentBranchRef()?.let { ref -> - ref.shortName().getOrNull()?.let { - GitRef.Branch(it, ref) - } - } - } - - override fun branchVersion(currentBranch: GitRef.Branch, targetBranch: GitRef.Branch): Result { - return git.calculateBaseBranchVersion(targetBranch, currentBranch, tags) - } - - override fun commitsSinceBranchPoint(currentBranch: GitRef.Branch, targetBranch: GitRef.Branch): Result { - return git.latestCommitOnBranch(currentBranch).map { branchPoint -> - gitCommitsSinceBranchPoint(git, branchPoint, targetBranch, tags).getOrThrow() - } - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/semver/GradleSemverContext.kt b/src/main/kotlin/com/figure/gradle/semver/internal/semver/GradleSemverContext.kt deleted file mode 100644 index fc7d1e5..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/semver/GradleSemverContext.kt +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.semver - -import com.figure.gradle.semver.external.ContextProviderOperations -import com.figure.gradle.semver.external.SemverContext - -internal class GradleSemverContext( - override val ops: ContextProviderOperations, -) : SemverContext diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculator.kt b/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculator.kt deleted file mode 100644 index be0223f..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculator.kt +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.semver - -import com.figure.gradle.semver.external.ContextProviderOperations -import com.figure.gradle.semver.external.SemverContext -import com.figure.gradle.semver.external.VersionModifier -import com.figure.gradle.semver.internal.exceptions.MissingBranchMatchingConfigurationException -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.internal.semverError -import com.figure.gradle.semver.internal.semverInfo -import com.figure.gradle.semver.internal.semverWarn -import net.swiftzer.semver.SemVer -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging - -private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) - -internal interface VersionCalculator { - fun calculateVersion(): Result -} - -internal class TargetBranchVersionCalculator( - private val contextProviderOperations: ContextProviderOperations, - private val config: VersionCalculatorConfig, - private val context: SemverContext, - private val currentBranch: GitRef.Branch, -) : VersionCalculator { - override fun calculateVersion(): Result { - return previousVersion().map { - versionQualifier(versionModifier(it)) - } - } - - private fun previousVersion(): Result { - return config.branchMatching - .firstOrNull { it.regex.matches(currentBranch.name) } - ?.let { bmc -> - log.semverInfo("Using BranchMatchingConfiguration: $bmc for previousVersion() with currentBranch: $currentBranch") - contextProviderOperations.branchVersion(currentBranch, bmc.targetBranch).map { semver -> - log.semverInfo("Branch version for current $currentBranch and target ${bmc.targetBranch}: $semver") - semver ?: run { - log.semverWarn("No version found for target branch ${bmc.targetBranch}, using initial version") - config.initialVersion - } - } - } - ?: run { - log.semverWarn("No match found for $currentBranch in ${config.branchMatching}, using initial version as previous version") - Result.failure(MissingBranchMatchingConfigurationException(currentBranch)) - } - } - - private fun versionModifier(current: SemVer): SemVer { - return config.branchMatching - .firstOrNull { it.regex.matches(currentBranch.name) } - ?.let { bmc -> - log.semverInfo("Using BranchMatchingConfiguration: $bmc for versionModifier() with currentBranch: $currentBranch") - val fn = bmc.versionModifier - current.fn() - } - ?: run { - log.semverWarn("No match found for $currentBranch in ${config.branchMatching}, using initial version as modified version") - config.initialVersion - } - } - - private fun versionQualifier(current: SemVer): SemVer { - return config.branchMatching - .firstOrNull { it.regex.matches(currentBranch.name) } - ?.let { bmc -> - log.semverInfo("Using BranchMatchingConfiguration: $bmc for versionQualifier() with currentBranch $currentBranch") - val fn = bmc.versionQualifier - context.fn(currentBranch).let { - current.copy( - preRelease = it.first.value.ifBlank { null }, - buildMetadata = it.second.value.ifBlank { null }, - ) - } - } - ?: run { - log.semverWarn("No match found for $currentBranch in ${config.branchMatching}") - current - } - } -} - -internal fun versionModifierFromString(modifier: String): VersionModifier { - return when (val mod = modifier.trim().lowercase()) { - "major" -> SemVer::nextMajor - "minor" -> SemVer::nextMinor - "patch" -> SemVer::nextPatch - else -> { - log.semverError("Unknown version modifier [$mod]") - throw Exception("Unknown version modifier [$modifier]") - } - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculatorConfig.kt b/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculatorConfig.kt deleted file mode 100644 index b93fb87..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/semver/VersionCalculatorConfig.kt +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.semver - -import com.figure.gradle.semver.external.VersionCalculatorStrategy -import com.figure.gradle.semver.external.mainBasedFlowVersionCalculatorStrategy -import net.swiftzer.semver.SemVer - -internal data class VersionCalculatorConfig( - val tagPrefix: String, - val initialVersion: SemVer = SemVer(0, 0, 1), - val overrideVersion: SemVer? = null, - val branchMatching: VersionCalculatorStrategy = mainBasedFlowVersionCalculatorStrategy { nextPatch() }, -) { - companion object { - internal val DEFAULT_VERSION = SemVer(0, 1, 0, null, null) - internal const val DEFAULT_TAG_PREFIX = "v" - } - - fun withBranchMatchingConfig(branchMatching: VersionCalculatorStrategy): VersionCalculatorConfig { - return this.copy(branchMatching = branchMatching) - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CreateAndPushVersionTagTask.kt b/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CreateAndPushVersionTagTask.kt deleted file mode 100644 index 15f22dd..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CreateAndPushVersionTagTask.kt +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.tasks - -import com.figure.gradle.semver.internal.exceptions.TagAlreadyExistsException -import com.figure.gradle.semver.internal.semverLifecycle -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.storage.file.FileRepositoryBuilder -import org.gradle.api.DefaultTask -import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskAction -import java.io.File - -@CacheableTask -abstract class CreateAndPushVersionTagTask : DefaultTask() { - @get:Input - abstract val versionTagName: Property - - @get:InputDirectory - @get:PathSensitive(PathSensitivity.ABSOLUTE) - abstract val gitDir: Property - - @TaskAction - fun createAndPushTag() { - val git = - Git( - FileRepositoryBuilder() - .setGitDir(gitDir.get()) - .readEnvironment() - .findGitDir() - .build(), - ) - - val tags = git.tagList().call() - val tagAlreadyExists = versionTagName.get() in tags.map { it.name.replace("refs/tags/", "") } - - if (tagAlreadyExists) throw TagAlreadyExistsException(versionTagName.get()) - - git.tag().setName(versionTagName.get()).call() - git.push().setPushTags().call() - logger.semverLifecycle("Created and pushed version tag: ${versionTagName.get()}") - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CurrentSemverTask.kt b/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CurrentSemverTask.kt deleted file mode 100644 index 45a6cc0..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/CurrentSemverTask.kt +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.tasks - -import com.figure.gradle.semver.internal.semverLifecycle -import org.gradle.api.DefaultTask -import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction - -@CacheableTask -abstract class CurrentSemverTask : DefaultTask() { - @get:Input - abstract val version: Property - - @get:Input - abstract val versionTagName: Property - - @TaskAction - fun currentSemver() { - logger.semverLifecycle("version: ${version.get()}") - logger.semverLifecycle("versionTagName: ${versionTagName.get()}") - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/GenerateVersionFileTask.kt b/src/main/kotlin/com/figure/gradle/semver/internal/tasks/GenerateVersionFileTask.kt deleted file mode 100644 index 0672929..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/tasks/GenerateVersionFileTask.kt +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -@CacheableTask -abstract class GenerateVersionFileTask : DefaultTask() { - @get:OutputFile - abstract val destination: RegularFileProperty - - @get:Input - abstract val version: Property - - @get:Input - abstract val versionTagName: Property - - @TaskAction - fun generateVersionFile() { - val file = destination.get().asFile - - file.apply { - parentFile.mkdirs() - createNewFile() - writeText( - """ - |${version.get()} - |${versionTagName.get()} - """.trimMargin(), - ) - } - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/valuesources/GitCalculateSemverValueSource.kt b/src/main/kotlin/com/figure/gradle/semver/internal/valuesources/GitCalculateSemverValueSource.kt deleted file mode 100644 index 9fc8039..0000000 --- a/src/main/kotlin/com/figure/gradle/semver/internal/valuesources/GitCalculateSemverValueSource.kt +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.internal.valuesources - -import com.figure.gradle.semver.external.BranchMatchingConfiguration -import com.figure.gradle.semver.external.VersionModifier -import com.figure.gradle.semver.external.mainBasedFlatVersionCalculatorStrategy -import com.figure.gradle.semver.external.mainBasedFlowVersionCalculatorStrategy -import com.figure.gradle.semver.external.masterBasedFlatVersionCalculatorStrategy -import com.figure.gradle.semver.external.masterBasedFlowVersionCalculatorStrategy -import com.figure.gradle.semver.internal.exceptions.UnsupportedBranchingStrategy -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.internal.git.hasBranch -import com.figure.gradle.semver.internal.git.openGitDir -import com.figure.gradle.semver.internal.semver.GitContextProviderOperations -import com.figure.gradle.semver.internal.semver.GradleSemverContext -import com.figure.gradle.semver.internal.semver.TargetBranchVersionCalculator -import com.figure.gradle.semver.internal.semver.VersionCalculatorConfig -import com.figure.gradle.semver.internal.semverError -import com.figure.gradle.semver.internal.semverInfo -import com.figure.gradle.semver.internal.semverLifecycle -import net.swiftzer.semver.SemVer -import org.eclipse.jgit.api.Git -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.provider.ValueSource -import org.gradle.api.provider.ValueSourceParameters - -private val log = Logging.getLogger(Logger.ROOT_LOGGER_NAME) - -/** - * In order to utilize the ValueSource, it needs to be wrapped in a Provider so that the value can be fetched - * from it later. - */ -internal fun ProviderFactory.gitCalculateSemverProvider( - gitDir: Property, - tagPrefix: Property, - initialVersion: String, - overrideVersion: String?, - versionStrategy: ListProperty, - versionModifier: Property, -): Provider { - return of(GitCalculateSemverValueSource::class.java) { spec -> - spec.parameters { - it.gitDir.set(gitDir) - it.tagPrefix.set(tagPrefix) - it.initialVersion.set(initialVersion) - it.overrideVersion.set(overrideVersion) - it.versionStrategy.set(versionStrategy) - it.versionModifier.set(versionModifier) - } - } -} - -/** - * The logic to calculate the next semantic version needs to be wrapped in a ValueSource since JGit makes external - * system calls to the git command line. - * - * In short, a ValueSource represents an external source of information used by a Gradle build. As of Gradle 8.1, - * this semver plugin would not work without this due to the requirements of the configuration cache to not allow - * external calls at configuration time. - * - * The following discussion on the Gradle forums goes into more details about the problem: - * https://discuss.gradle.org/t/using-jgit-with-gradle-configuration-cache/45410/1 - * - * For more information on ValueSource, see the documentation about "Running external processes" with configuration - * cache enable: https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements:external_processes - */ -internal abstract class GitCalculateSemverValueSource : ValueSource { - interface Params : ValueSourceParameters { - val gitDir: Property - val tagPrefix: Property - val initialVersion: Property - val overrideVersion: Property - val versionStrategy: ListProperty - val versionModifier: Property - } - - override fun obtain(): String? = calculateVersion().toString() - - private fun calculateVersion(): SemVer { - val gitDir = parameters.gitDir.get() - - log.semverInfo("Using git directory: $gitDir") - - val git = openGitDir(gitDir) - val config = buildCalculatorConfig(git) - val ops = GitContextProviderOperations(git, config) - val context = GradleSemverContext(ops) - - return config.overrideVersion ?: run { - ops.currentBranch()?.let { currentBranch -> - log.semverInfo("Current branch: $currentBranch") - log.semverInfo("Semver configuration while calculating version: $config") - - val calculator = TargetBranchVersionCalculator(ops, config, context, currentBranch) - calculator.calculateVersion().getOrElse { - log.semverError("Failed to calculate version", it) - throw Exception(it) - } - } ?: run { - log.semverError("Failed to find current branch, cannot calculate semver") - throw Exception("Failed to find current branch") - } - }.also { - log.semverLifecycle("$it") - } - } - - private fun buildCalculatorConfig(git: Git): VersionCalculatorConfig { - val initialConfig = VersionCalculatorConfig( - tagPrefix = parameters.tagPrefix.get(), - initialVersion = SemVer.parse(parameters.initialVersion.get()), - overrideVersion = parameters.overrideVersion.orNull?.let { SemVer.parse(it) }, - ) - - val versionStrategy = parameters.versionStrategy - val versionModifier = parameters.versionModifier - return when { - versionStrategy.isPresent -> { - log.semverInfo("Enabling extension configured strategy") - initialConfig.withBranchMatchingConfig(versionStrategy.get()) - } - - git.hasBranch(GitRef.Branch.DEVELOP) && git.hasBranch(GitRef.Branch.MAIN) -> { - log.semverInfo("Enabling Git Flow mode for develop and main") - initialConfig.withBranchMatchingConfig(mainBasedFlowVersionCalculatorStrategy(versionModifier.get())) - } - - git.hasBranch(GitRef.Branch.DEVELOP) && git.hasBranch(GitRef.Branch.MASTER) -> { - log.semverInfo("Enabling Git Flow mode for develop and master") - initialConfig.withBranchMatchingConfig(masterBasedFlowVersionCalculatorStrategy(versionModifier.get())) - } - - git.hasBranch(GitRef.Branch.MAIN) -> { - log.semverInfo("Enabling main-based Flat mode") - initialConfig.withBranchMatchingConfig(mainBasedFlatVersionCalculatorStrategy(versionModifier.get())) - } - - git.hasBranch(GitRef.Branch.MASTER) -> { - log.semverInfo("Enabling master-based Flat mode") - initialConfig.withBranchMatchingConfig(masterBasedFlatVersionCalculatorStrategy(versionModifier.get())) - } - - else -> { - log.semverError("Could not determine branching strategy. No version calculation will be performed.") - throw UnsupportedBranchingStrategy() - } - } - } -} diff --git a/src/main/kotlin/com/figure/gradle/semver/internal/writer/VersionWriter.kt b/src/main/kotlin/com/figure/gradle/semver/internal/writer/VersionWriter.kt new file mode 100644 index 0000000..8041d76 --- /dev/null +++ b/src/main/kotlin/com/figure/gradle/semver/internal/writer/VersionWriter.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.writer + +import com.figure.gradle.semver.Constants +import com.figure.gradle.semver.internal.extensions.getOrCreate +import com.figure.gradle.semver.internal.extensions.projectDir +import org.gradle.api.plugins.PluginAware + +fun PluginAware.writeVersionToPropertiesFile( + version: String, + tagPrefix: String, +) { + projectDir.resolve(Constants.SEMVER_PROPERTY_PATH).getOrCreate().writeText( + """ + |version=$version + |versionTag=$tagPrefix$version + """.trimMargin(), + ) +} diff --git a/src/test/kotlin/com/figure/gradle/semver/integration/CreateAndPushVersionTagTaskSpec.kt b/src/test/kotlin/com/figure/gradle/semver/integration/CreateAndPushVersionTagTaskSpec.kt deleted file mode 100644 index 9567e08..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/integration/CreateAndPushVersionTagTaskSpec.kt +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.integration - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.testkit.GradleIntegrationTestKitExtension -import com.figure.gradle.semver.util.GradleArgs -import com.figure.gradle.semver.util.taskOutcome -import io.kotest.core.spec.style.FunSpec -import io.kotest.datatest.withData -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome - -class CreateAndPushVersionTagTaskSpec : FunSpec({ - val runner = GradleRunner.create() - - val task = "createAndPushVersionTag" - val defaultArguments = listOf("build", task, GradleArgs.STACKTRACE) - - fun GradleRunner.runWithoutExpectations(arguments: List): Pair { - val firstRun = this - .withArguments(arguments) - .build() - - val secondRun = this - .withArguments(arguments) - .run() - - return firstRun to secondRun - } - - context("create and push version tag") { - val listeners = listOf( - GradleIntegrationTestKitExtension( - runner, - initialBranch = GitRef.Branch.MAIN, - ), - GradleIntegrationTestKitExtension( - runner, - initialBranch = GitRef.Branch.MASTER, - ), - GradleIntegrationTestKitExtension( - runner, - initialBranch = GitRef.Branch.MAIN, - defaultBranch = GitRef.Branch.DEVELOP, - ), - GradleIntegrationTestKitExtension( - runner, - initialBranch = GitRef.Branch.MASTER, - defaultBranch = GitRef.Branch.DEVELOP, - ), - ) - - val testData = listOf( - TestData(listOf()), - TestData(listOf(GradleArgs.PARALLEL)), - TestData(listOf(GradleArgs.BUILD_CACHE)), - TestData(listOf(GradleArgs.CONFIGURATION_CACHE)), - TestData(listOf(GradleArgs.PARALLEL, GradleArgs.BUILD_CACHE, GradleArgs.CONFIGURATION_CACHE)), - ) - - listeners.forEach { listener -> - listener(listener) - - withData( - nameFn = { - if (listener.defaultBranch != null) { - "${listener.initialBranch.name}-${listener.defaultBranch.name} -- args: ${it.additionalArgs}" - } else { - "${listener.initialBranch.name} -- args: ${it.additionalArgs}" - } - }, - testData.asSequence(), - ) { - // Given - val arguments = defaultArguments + it.additionalArgs - - // When - val (firstRun, secondRun) = runner.runWithoutExpectations(arguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.FAILED - secondRun.output shouldContain "TagAlreadyExistsException" - } - } - } -}) - -private data class TestData( - val additionalArgs: List, -) diff --git a/src/test/kotlin/com/figure/gradle/semver/integration/CurrentSemverTaskSpec.kt b/src/test/kotlin/com/figure/gradle/semver/integration/CurrentSemverTaskSpec.kt deleted file mode 100644 index 2483241..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/integration/CurrentSemverTaskSpec.kt +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.integration - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.testkit.GradleIntegrationTestKitExtension -import com.figure.gradle.semver.util.GradleArgs -import com.figure.gradle.semver.util.runTask -import com.figure.gradle.semver.util.taskOutcome -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome - -class CurrentSemverTaskSpec : FunSpec({ - val runner = GradleRunner.create() - - val gradleIntegrationTestKitExtension = GradleIntegrationTestKitExtension(runner, GitRef.Branch.MAIN) - listener(gradleIntegrationTestKitExtension) - - val task = "currentSemver" - val defaultArguments = listOf("build", task, GradleArgs.STACKTRACE) - - test("build") { - // When - val (firstRun, secondRun) = runner.runTask(defaultArguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - - firstRun.output shouldContain "version: 9.9.9" - secondRun.output shouldContain "version: 9.9.9" - } - - test("with parallel") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.PARALLEL) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - - firstRun.output shouldContain "version: 9.9.9" - secondRun.output shouldContain "version: 9.9.9" - } - - test("with build-cache") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.BUILD_CACHE) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - - firstRun.output shouldContain "version: 9.9.9" - secondRun.output shouldContain "version: 9.9.9" - } - - test("with configuration-cache") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.CONFIGURATION_CACHE) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.output shouldContain "Calculating task graph as no cached configuration is available for tasks" - secondRun.output shouldContain "Reusing configuration cache." - - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - - firstRun.output shouldContain "version: 9.9.9" - secondRun.output shouldContain "version: 9.9.9" - } - - test("with parallel, build-cache, and configuration-cache") { - // Given - val arguments = - defaultArguments + - listOf( - GradleArgs.PARALLEL, - GradleArgs.BUILD_CACHE, - GradleArgs.CONFIGURATION_CACHE, - ) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.output shouldContain "Calculating task graph as no cached configuration is available for tasks" - secondRun.output shouldContain "Reusing configuration cache." - - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - - firstRun.output shouldContain "version: 9.9.9" - secondRun.output shouldContain "version: 9.9.9" - } -}) diff --git a/src/test/kotlin/com/figure/gradle/semver/integration/GenerateVersionFileTaskSpec.kt b/src/test/kotlin/com/figure/gradle/semver/integration/GenerateVersionFileTaskSpec.kt deleted file mode 100644 index 7e02548..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/integration/GenerateVersionFileTaskSpec.kt +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.integration - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.testkit.GradleIntegrationTestKitExtension -import com.figure.gradle.semver.util.GradleArgs -import com.figure.gradle.semver.util.runTask -import com.figure.gradle.semver.util.taskOutcome -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.file.exist -import io.kotest.matchers.should -import io.kotest.matchers.shouldBe -import io.kotest.matchers.string.shouldContain -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome - -class GenerateVersionFileTaskSpec : FunSpec({ - val runner = GradleRunner.create() - - val gradleIntegrationTestKitExtension = GradleIntegrationTestKitExtension(runner, GitRef.Branch.MAIN) - listener(gradleIntegrationTestKitExtension) - - val task = "generateVersionFile" - val defaultArguments = listOf("build", task, GradleArgs.STACKTRACE) - - fun validateVersionFile() { - val versionFile = gradleIntegrationTestKitExtension.tempRepoDir.resolve("build/semver/version.txt") - - versionFile should exist() - versionFile.readText() shouldContain "9.9.9" - } - - test("build") { - // When - val (firstRun, secondRun) = runner.runTask(defaultArguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.UP_TO_DATE - - validateVersionFile() - } - - test("with parallel") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.PARALLEL) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.UP_TO_DATE - - validateVersionFile() - } - - test("with build-cache") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.BUILD_CACHE) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.UP_TO_DATE - - validateVersionFile() - } - - test("with configuration-cache") { - // Given - val arguments = defaultArguments + listOf(GradleArgs.CONFIGURATION_CACHE) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.output shouldContain "Calculating task graph as no cached configuration is available for tasks" - - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.UP_TO_DATE - - validateVersionFile() - } - - test("with parallel, build-cache, and configuration-cache") { - // Given - val arguments = - defaultArguments + - listOf( - GradleArgs.PARALLEL, - GradleArgs.BUILD_CACHE, - GradleArgs.CONFIGURATION_CACHE, - ) - - // When - val (firstRun, secondRun) = runner.runTask(arguments) - - // Then - firstRun.output shouldContain "Calculating task graph as no cached configuration is available for tasks" - - firstRun.taskOutcome(task) shouldBe TaskOutcome.SUCCESS - secondRun.taskOutcome(task) shouldBe TaskOutcome.UP_TO_DATE - - validateVersionFile() - } -}) diff --git a/src/test/kotlin/com/figure/gradle/semver/integration/StandardComputeNextVersionSpec.kt b/src/test/kotlin/com/figure/gradle/semver/integration/StandardComputeNextVersionSpec.kt deleted file mode 100644 index 3c8f624..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/integration/StandardComputeNextVersionSpec.kt +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.integration - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.testkit.GradleIntegrationTestKitExtension -import com.figure.gradle.semver.util.GradleArgs -import com.figure.gradle.semver.util.NEXT_PATCH_VERSION -import com.figure.gradle.semver.util.resourceFromPath -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.string.shouldContain -import org.gradle.testkit.runner.GradleRunner - -/** - * These tests are disabled since there is an issue with calculating the current branch when running in GitHub Actions. - * - * When the plugin executes to calculate the current branch, we have to take into account whether we're in a - * GitHub Action since the format for the "current branch" when executing from a pull request is - * "refs/pull//merge". Obviously, we don't want to use this as the "current branch" when we're building an - * actual project. - * - * The problem comes in when we run a test. We have these environment variables set for GITHUB_* and so we utilize them - * during a test run. This causes the incorrect branch to be fetched. That is, the PR branch gets used instead of - * the branch current branch in the GradleRunner. - * - * TL;DR - Tests should work locally, tests will not work in GitHub Actions :( - */ -class StandardComputeNextVersionSpec : FunSpec({ - - val runner = GradleRunner.create() - - val gradleIntegrationTestKitExtension = - GradleIntegrationTestKitExtension( - runner = runner, - initialBranch = GitRef.Branch.MAIN, - defaultBranch = GitRef.Branch.DEVELOP, - buildFile = resourceFromPath("integration/standard-project/build.gradle.kts"), - settingsFile = resourceFromPath("integration/standard-project/settings.gradle.kts"), - ) - - listener(gradleIntegrationTestKitExtension) - - xtest("should compute next version") { - // Given - val git = gradleIntegrationTestKitExtension.git - - git.commit().setMessage("Empty commit").setAllowEmpty(true).call() - git.push().call() - - // When - val buildResult = - runner - .withArguments(GradleArgs.STACKTRACE) - .build() - - // Then - buildResult.output shouldContain NEXT_PATCH_VERSION - } - - xtest("should compute next version with additional params") { - // Given - val git = gradleIntegrationTestKitExtension.git - - git.commit().setMessage("Empty commit").setAllowEmpty(true).call() - git.push().call() - - // When - val buildResult = - runner - .withArguments( - GradleArgs.STACKTRACE, - GradleArgs.PARALLEL, - GradleArgs.BUILD_CACHE, - GradleArgs.CONFIGURATION_CACHE, - ) - .build() - - // Then - buildResult.output shouldContain NEXT_PATCH_VERSION - } -}) diff --git a/src/test/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtensionKtSpec.kt b/src/test/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtensionKtSpec.kt new file mode 100644 index 0000000..4f0b415 --- /dev/null +++ b/src/test/kotlin/com/figure/gradle/semver/internal/extensions/VersionExtensionKtSpec.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024 Figure Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.figure.gradle.semver.internal.extensions + +import com.figure.gradle.semver.internal.properties.Modifier +import com.figure.gradle.semver.internal.properties.Stage +import io.github.z4kn4fein.semver.Version +import io.github.z4kn4fein.semver.toVersion +import io.kotest.assertions.throwables.shouldThrowAny +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe + +class VersionExtensionKtSpec : FunSpec({ + test("invalid version") { + val inputVersion = "1.0.0-my-cool-branch".toVersion() + shouldThrowAny { + inputVersion.nextVersion(Stage.Auto, Modifier.Auto) + } + } + + context("next version from stable") { + withData( + Stage.Dev, + Stage.Alpha, + Stage.Beta, + Stage.ReleaseCandidate, + Stage.Final, + Stage.GA, + Stage.Release, + ) { stage -> + val inputVersion = "1.0.0".toVersion() + withData( + TestData(Modifier.Major, "2.0.0-${stage.value}.1"), + TestData(Modifier.Minor, "1.1.0-${stage.value}.1"), + TestData(Modifier.Patch, "1.0.1-${stage.value}.1"), + TestData(Modifier.Auto, "1.0.1-${stage.value}.1"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + + withData( + Stage.Stable, + Stage.Auto, + ) { stage -> + val inputVersion = "1.0.0".toVersion() + withData( + TestData(Modifier.Major, "2.0.0"), + TestData(Modifier.Minor, "1.1.0"), + TestData(Modifier.Patch, "1.0.1"), + TestData(Modifier.Auto, "1.0.1"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + + context(Stage.Snapshot.name) { + val inputVersion = "1.0.0".toVersion() + val stage = Stage.Snapshot + withData( + TestData(Modifier.Major, "2.0.0-${stage.value}"), + TestData(Modifier.Minor, "1.1.0-${stage.value}"), + TestData(Modifier.Patch, "1.0.1-${stage.value}"), + TestData(Modifier.Auto, "1.0.1-${stage.value}"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + } + + context("next version from pre-release") { + withData( + Stage.Dev, + Stage.Alpha, + Stage.Beta, + Stage.ReleaseCandidate, + Stage.Final, + Stage.GA, + Stage.Release, + ) { stage -> + val inputVersion = "1.0.0-${stage.value}.1".toVersion() + + withData( + TestData(Modifier.Major, "2.0.0-${stage.value}.1"), + TestData(Modifier.Minor, "1.1.0-${stage.value}.1"), + TestData(Modifier.Patch, "1.0.1-${stage.value}.1"), + TestData(Modifier.Auto, "1.0.0-${stage.value}.2"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + + context(Stage.Stable.name) { + val stage = Stage.Stable + val inputVersion = "1.0.0-alpha.1".toVersion() + + withData( + TestData(Modifier.Major, "2.0.0"), + TestData(Modifier.Minor, "1.1.0"), + TestData(Modifier.Patch, "1.0.0"), + TestData(Modifier.Auto, "1.0.0"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + + context(Stage.Auto.name) { + val stage = Stage.Auto + val inputVersion = "1.0.0-alpha.1".toVersion() + + withData( + TestData(Modifier.Major, "2.0.0-alpha.1"), + TestData(Modifier.Minor, "1.1.0-alpha.1"), + TestData(Modifier.Patch, "1.0.1-alpha.1"), + TestData(Modifier.Auto, "1.0.0-alpha.2"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + + context("different pre-release") { + withData( + Stage.Dev, + Stage.Beta, + Stage.ReleaseCandidate, + Stage.Final, + Stage.GA, + Stage.Release, + ) { stage -> + val inputVersion = "1.0.0-alpha.1".toVersion() + withData( + TestData(Modifier.Major, "2.0.0-${stage.value}.1"), + TestData(Modifier.Minor, "1.1.0-${stage.value}.1"), + TestData(Modifier.Patch, "1.0.1-${stage.value}.1"), + TestData(Modifier.Auto, "1.0.1-${stage.value}.1"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + } + + context(Stage.Snapshot.name) { + val inputVersion = "1.0.0-alpha.1".toVersion() + val stage = Stage.Snapshot + withData( + TestData(Modifier.Major, "2.0.0-${stage.value}"), + TestData(Modifier.Minor, "1.1.0-${stage.value}"), + TestData(Modifier.Patch, "1.0.1-${stage.value}"), + TestData(Modifier.Auto, "1.0.1-${stage.value}"), + ) { + inputVersion.nextVersion(stage, it.modifier) shouldBe it.expectedVersion + } + } + } +}) + +internal data class TestData( + val modifier: Modifier, + private val expectedVersionString: String, +) { + val expectedVersion: Version + get() = expectedVersionString.toVersion() +} diff --git a/src/test/kotlin/com/figure/gradle/semver/testkit/GradleIntegrationTestKitExtension.kt b/src/test/kotlin/com/figure/gradle/semver/testkit/GradleIntegrationTestKitExtension.kt deleted file mode 100644 index 4d602c2..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/testkit/GradleIntegrationTestKitExtension.kt +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.testkit - -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.util.appendFileContents -import com.figure.gradle.semver.util.copyToDir -import com.figure.gradle.semver.util.initializeWithCommitsAndTags -import com.figure.gradle.semver.util.resourceFromPath -import com.figure.gradle.semver.util.toFile -import io.kotest.core.listeners.TestListener -import io.kotest.core.test.TestCase -import io.kotest.core.test.TestResult -import org.eclipse.jgit.api.Git -import org.gradle.testkit.runner.GradleRunner -import java.io.File -import kotlin.io.path.createTempDirectory - -class GradleIntegrationTestKitExtension( - private val runner: GradleRunner, - val initialBranch: GitRef.Branch, - val defaultBranch: GitRef.Branch? = null, - private val kotlinVersion: KotlinVersion = KotlinVersion.CURRENT, - private val buildFile: File = resourceFromPath("integration/basic-project/build.gradle.kts"), - private val settingsFile: File = resourceFromPath("integration/basic-project/settings.gradle.kts"), -) : TestListener { - lateinit var tempRepoDir: File - lateinit var tempRemoteRepoDir: File - lateinit var localBuildCacheDirectory: File - lateinit var git: Git - - override suspend fun beforeAny(testCase: TestCase) { - if (::tempRepoDir.isInitialized) { - tempRepoDir.deleteRecursively() - tempRemoteRepoDir.deleteRecursively() - localBuildCacheDirectory.deleteRecursively() - git.close() - } - - tempRepoDir = createTempDirectory("tempRepoDir").toFile() - tempRemoteRepoDir = createTempDirectory("tempRemoteRepoDir").toFile() - - val updatedBuildFile = - buildFile.readText().replace("@kotlin-version@", kotlinVersion.toString()) - .toFile("${tempRepoDir.path}/build/updated/build.gradle.kts") - - updatedBuildFile.copyToDir(tempRepoDir, "build.gradle.kts") - - localBuildCacheDirectory = File(tempRepoDir, "local-cache") - val updatedSettingsFile = - settingsFile.appendFileContents( - """ - buildCache { - local { - directory = "${localBuildCacheDirectory.toURI()}" - } - } - """.trimMargin(), - ) - - updatedSettingsFile.copyToDir(tempRepoDir, "settings.gradle.kts") - - // Initialize temp directory as a "repo" - git = Git.init().setDirectory(tempRepoDir).setInitialBranch(initialBranch.name).call() - git.initializeWithCommitsAndTags(tempRepoDir, tempRemoteRepoDir, initialBranch, defaultBranch) - - runner.forwardOutput() - .withProjectDir(File(tempRepoDir.path.toString())) - .withPluginClasspath() - } - - override suspend fun afterAny( - testCase: TestCase, - result: TestResult, - ) { - } -} diff --git a/src/test/kotlin/com/figure/gradle/semver/unit/BranchSpec.kt b/src/test/kotlin/com/figure/gradle/semver/unit/BranchSpec.kt deleted file mode 100644 index 3d38d95..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/unit/BranchSpec.kt +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.unit - -import com.figure.gradle.semver.internal.git.GitRef -import io.kotest.core.spec.style.WordSpec -import io.kotest.matchers.shouldBe - -class BranchSpec : WordSpec({ - "Branch" should { - "sanitize names correctly" { - GitRef.Branch("feature/something.5-bla\$bla").sanitizedNameWithoutPrefix() shouldBe "something.5-bla-bla" - GitRef.Branch("feature/something_other.5").sanitizedNameWithoutPrefix() shouldBe "something_other.5" - GitRef.Branch("something_other.5").sanitizedNameWithoutPrefix() shouldBe "something_other.5" - } - } -}) diff --git a/src/test/kotlin/com/figure/gradle/semver/unit/SemverSpec.kt b/src/test/kotlin/com/figure/gradle/semver/unit/SemverSpec.kt deleted file mode 100644 index 3cd8cc9..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/unit/SemverSpec.kt +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.unit - -import com.figure.gradle.semver.internal.git.semverTag -import io.kotest.core.spec.style.WordSpec -import io.kotest.matchers.nulls.beNull -import io.kotest.matchers.should -import io.kotest.matchers.shouldBe -import net.swiftzer.semver.SemVer - -class SemverSpec : WordSpec({ - "Semver" should { - "match valid existing semver tags on refs" { - "refs/tags/v123".semverTag("v") should beNull() - "refs/tags/v1.2.3".semverTag("v") shouldBe SemVer(1, 2, 3) - "refs/tags/v0.1.2-main".semverTag("v") shouldBe SemVer(0, 1, 2, "main") - - // Semver lib can't understand qualified labels apparently - // "refs/tags/v0.1.2-main.2".semverTag("v") shouldBe SemVer(0, 1, 2, "main.2") - // SemVer.parse("v0.1.2-main.2") shouldBe SemVer(0, 1, 2, "main.2") - } - } -}) diff --git a/src/test/kotlin/com/figure/gradle/semver/unit/VersionCalculatorSpec.kt b/src/test/kotlin/com/figure/gradle/semver/unit/VersionCalculatorSpec.kt deleted file mode 100644 index 4b3bd41..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/unit/VersionCalculatorSpec.kt +++ /dev/null @@ -1,334 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.unit - -import com.figure.gradle.semver.external.BranchMatchingConfiguration -import com.figure.gradle.semver.external.BuildMetadataLabel -import com.figure.gradle.semver.external.ContextProviderOperations -import com.figure.gradle.semver.external.PreReleaseLabel -import com.figure.gradle.semver.external.SemverContext -import com.figure.gradle.semver.external.VersionModifier -import com.figure.gradle.semver.external.mainBasedFlatVersionCalculatorStrategy -import com.figure.gradle.semver.external.mainBasedFlowVersionCalculatorStrategy -import com.figure.gradle.semver.external.masterBasedFlatVersionCalculatorStrategy -import com.figure.gradle.semver.internal.git.GitRef -import com.figure.gradle.semver.internal.semver.TargetBranchVersionCalculator -import com.figure.gradle.semver.internal.semver.VersionCalculatorConfig -import io.kotest.core.spec.style.FunSpec -import io.kotest.datatest.withData -import io.kotest.matchers.shouldBe -import net.swiftzer.semver.SemVer - -class VersionCalculatorSpec : FunSpec({ - fun calculateBranchVersion( - currentBranch: GitRef.Branch, - branchVersions: Map, - config: VersionCalculatorConfig, - commitsSinceBranchPoint: Int = 2, - ): Result { - val ops = getMockContextProviderOperations(currentBranch, branchVersions, commitsSinceBranchPoint) - val context = mockSemVerContext(ops) - val calculator = TargetBranchVersionCalculator(ops, config, context, currentBranch) - return calculator.calculateVersion() - } - - context("Calculate version with mainBasedFlatDefaultBranchMatching") { - context("calculate the next version correctly") { - val mainBranchVersion = SemVer(1, 2, 3) - val mainBranch = GitRef.Branch.MAIN - val config = buildPluginConfig(mainBasedFlatVersionCalculatorStrategy { nextPatch() }) - - val branchVersions: Map = - mapOf( - mainBranch to mainBranchVersion, - ) - - withData( - calculateBranchVersion(mainBranch, branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch(), - calculateBranchVersion(GitRef.Branch("feature/my_weird_feature"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().copy(preRelease = "my_weird_feature.2"), - calculateBranchVersion(GitRef.Branch("something"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().copy(preRelease = "something.2"), - calculateBranchVersion(GitRef.Branch("rc/fix-1"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().copy(preRelease = "rc.2"), - ) { (calculatedVersion, expectedVersion) -> - calculatedVersion shouldBe expectedVersion - } - } - } - - context("Calculate version with masterBasedFlatDefaultBranchMatching") { - context("calculate the next version correctly") { - val masterBranchVersion = SemVer(1, 2, 3) - val masterBranch = GitRef.Branch.MASTER - val config = buildPluginConfig(masterBasedFlatVersionCalculatorStrategy { nextPatch() }) - - val branchVersions: Map = - mapOf( - masterBranch to masterBranchVersion, - ) - - withData( - calculateBranchVersion(masterBranch, branchVersions, config).getOrThrow() - to masterBranchVersion.nextPatch(), - calculateBranchVersion(GitRef.Branch("feature/my_weird_feature"), branchVersions, config).getOrThrow() - to masterBranchVersion.nextPatch().copy(preRelease = "my_weird_feature.2"), - calculateBranchVersion(GitRef.Branch("something"), branchVersions, config).getOrThrow() - to masterBranchVersion.nextPatch().copy(preRelease = "something.2"), - calculateBranchVersion(GitRef.Branch("rc/fix-1"), branchVersions, config).getOrThrow() - to masterBranchVersion.nextPatch().copy(preRelease = "rc.2"), - ) { (calculatedVersion, expectedVersion) -> - calculatedVersion shouldBe expectedVersion - } - } - } - - context("Calculate version with FlowDefaultBranchMatching") { - context("calculate the next version correctly") { - val mainBranchVersion = SemVer(1, 2, 3) - val mainBranch = GitRef.Branch.MAIN - val developBranchVersion = SemVer(1, 2, 4, "beta") - val developBranch = GitRef.Branch.DEVELOP - val config = buildPluginConfig(mainBasedFlowVersionCalculatorStrategy { nextPatch() }) - - val branchVersions: Map = - mapOf( - mainBranch to mainBranchVersion, - developBranch to developBranchVersion, - ) - - withData( - calculateBranchVersion(mainBranch, branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch(), - calculateBranchVersion(developBranch, branchVersions, config).getOrThrow() to - mainBranchVersion.nextPatch().copy(preRelease = "beta.2"), - calculateBranchVersion(GitRef.Branch("rc/something"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().copy(preRelease = "rc.2"), - calculateBranchVersion(GitRef.Branch("feature/s_something*bla"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().nextPatch().copy(preRelease = "s_something-bla.2"), - // Assert that any branch name gets matched properly (previously only feature/ was allowed) - calculateBranchVersion(GitRef.Branch("s_something*bla"), branchVersions, config).getOrThrow() - to mainBranchVersion.nextPatch().nextPatch().copy(preRelease = "s_something-bla.2"), - ) { (calculatedVersion, expectedVersion) -> - calculatedVersion shouldBe expectedVersion - } - } - - test("support custom formats such as ShortCut") { - val mainBranchVersion = SemVer(1, 2, 3) - val mainBranch = GitRef.Branch.MAIN - val developBranchVersion = SemVer(1, 2, 4, "beta") - val developBranch = GitRef.Branch.DEVELOP - val versionModifier: VersionModifier = { nextPatch() } - - val config = - buildPluginConfig( - listOf( - BranchMatchingConfiguration( - """^main$""".toRegex(), - GitRef.Branch.MAIN, - { PreReleaseLabel.EMPTY to BuildMetadataLabel.EMPTY }, - versionModifier, - ), - BranchMatchingConfiguration( - """^develop$""".toRegex(), - GitRef.Branch.MAIN, - { preReleaseWithCommitCount(it, GitRef.Branch.MAIN, "beta") to BuildMetadataLabel.EMPTY }, - versionModifier, - ), - BranchMatchingConfiguration( - """^feature/.*""".toRegex(), - GitRef.Branch.DEVELOP, - { current -> - preReleaseWithCommitCount( - currentBranch = current, - targetBranch = GitRef.Branch.MAIN, - label = current.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier, - ), - BranchMatchingConfiguration( - """^.+/sc-\d+/.+""".toRegex(), - GitRef.Branch.DEVELOP, - { current -> - preReleaseWithCommitCount( - currentBranch = current, - targetBranch = GitRef.Branch.MAIN, - label = current.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier, - ), - BranchMatchingConfiguration( - """^.+/\d+/.+""".toRegex(), - GitRef.Branch.DEVELOP, - { current -> - preReleaseWithCommitCount( - currentBranch = current, - targetBranch = GitRef.Branch.MAIN, - label = current.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier, - ), - BranchMatchingConfiguration( - """^.+/no-ticket/.+""".toRegex(), - GitRef.Branch.DEVELOP, - { current -> - preReleaseWithCommitCount( - currentBranch = current, - targetBranch = GitRef.Branch.MAIN, - label = current.sanitizedNameWithoutPrefix(), - ) to BuildMetadataLabel.EMPTY - }, - versionModifier, - ), - BranchMatchingConfiguration( - """^rc/.*""".toRegex(), - GitRef.Branch.MAIN, - { preReleaseWithCommitCount(it, GitRef.Branch.MAIN, "rc") to BuildMetadataLabel.EMPTY }, - versionModifier, - ), - ), - ) - - val branchVersions: Map = - mapOf( - mainBranch to mainBranchVersion, - developBranch to developBranchVersion, - ) - - // current == MAIN - calculateBranchVersion( - currentBranch = mainBranch, - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion.nextPatch(), - ) - - calculateBranchVersion( - currentBranch = developBranch, - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .copy(preRelease = "beta.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("rc/something"), - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .copy(preRelease = "rc.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("feature/something_something*bla"), - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .nextPatch() - .copy(preRelease = "something_something-bla.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("someuser/sc-145300/standardized-gradle-build"), - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .nextPatch() - .copy(preRelease = "sc-145300-standardized-gradle-build.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("someuser/no-ticket/standardized-gradle-build"), - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .nextPatch() - .copy(preRelease = "no-ticket-standardized-gradle-build.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("someuser/145300/standardized-gradle-build"), - branchVersions = branchVersions, - config = config, - ).getOrThrow().shouldBe( - mainBranchVersion - .nextPatch() - .nextPatch() - .copy(preRelease = "145300-standardized-gradle-build.2"), - ) - - calculateBranchVersion( - currentBranch = GitRef.Branch("someuser/abc/standardized-gradle-build"), - branchVersions = branchVersions, - config = config, - ).isFailure shouldBe true - } - } -}) - -private fun getMockContextProviderOperations( - currentBranch: GitRef.Branch, - branchVersion: Map, - commitsSinceBranchPoint: Int = 2, -): ContextProviderOperations = - object : ContextProviderOperations { - override fun currentBranch(): GitRef.Branch { - return currentBranch - } - - override fun branchVersion( - currentBranch: GitRef.Branch, - targetBranch: GitRef.Branch, - ): Result { - return Result.success( - branchVersion - .filter { it.key.name == targetBranch.name } - .toList() - .firstOrNull() - ?.second, - ) - } - - override fun commitsSinceBranchPoint( - currentBranch: GitRef.Branch, - targetBranch: GitRef.Branch, - ): Result { - return Result.success(commitsSinceBranchPoint) - } - } - -private fun buildPluginConfig(branchMatching: List): VersionCalculatorConfig { - return VersionCalculatorConfig( - "v", - initialVersion = SemVer(0, 0, 1), - branchMatching = branchMatching, - ) -} - -private fun mockSemVerContext(ops: ContextProviderOperations): SemverContext { - return object : SemverContext { - override val ops: ContextProviderOperations - get() = ops - } -} diff --git a/src/test/kotlin/com/figure/gradle/semver/util/BuildResultUtils.kt b/src/test/kotlin/com/figure/gradle/semver/util/BuildResultUtils.kt deleted file mode 100644 index 7496a3c..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/util/BuildResultUtils.kt +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.util - -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.TaskOutcome - -fun BuildResult.taskOutcome(taskName: String): TaskOutcome? { - return checkNotNull(task(":$taskName")).outcome -} diff --git a/src/test/kotlin/com/figure/gradle/semver/util/FileUtils.kt b/src/test/kotlin/com/figure/gradle/semver/util/FileUtils.kt deleted file mode 100644 index 62dcd0a..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/util/FileUtils.kt +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.util - -import java.io.File -import java.io.FileWriter -import java.nio.file.NotDirectoryException - -fun resourceFromPath(resourcePath: String): File = File(Thread.currentThread().contextClassLoader?.getResource(resourcePath)?.toURI()!!) - -fun String.toFile(path: String): File { - val newFile = File(path) - newFile.parentFile.mkdirs() - newFile.writeText(this) - return newFile -} - -fun File.copyToDir( - dest: File, - fileName: String = this.name, -) { - if (dest.isDirectory) { - val newFile = File("${dest.path}/$fileName") - this.copyTo(newFile) - } else { - throw NotDirectoryException(dest.path) - } -} - -fun File.appendFileContents(newContent: String): File { - FileWriter(this).use { - it.write(newContent) - } - return this -} diff --git a/src/test/kotlin/com/figure/gradle/semver/util/GitUtils.kt b/src/test/kotlin/com/figure/gradle/semver/util/GitUtils.kt deleted file mode 100644 index fd20b3a..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/util/GitUtils.kt +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.util - -import com.figure.gradle.semver.internal.git.GitRef -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.lib.PersonIdent -import org.eclipse.jgit.revwalk.RevCommit -import org.eclipse.jgit.transport.RefSpec -import org.eclipse.jgit.transport.URIish -import java.io.File - -const val NEXT_PATCH_VERSION = "1.2.1" -const val NEXT_MINOR_VERSION = "1.3.0" -const val NEXT_MAJOR_VERSION = "2.0.0" - -fun Git.initializeWithCommitsAndTags( - tempRepoDir: File, - tempGitRemoteDir: File, - initialBranch: GitRef.Branch, - defaultBranch: GitRef.Branch?, -) { - // create and commit 3 files in the repository and make a tag after each commit - addCommitsAndTags(initialBranch, tempRepoDir) - - if (defaultBranch != null) { - checkout().setCreateBranch(true).setName(defaultBranch.name).call() - addCommitsAndTags(defaultBranch, tempRepoDir) - } - - // create a temporary Git repository to use as the remote repository - tempRepoDir.copyRecursively(tempGitRemoteDir) - - // Create a RefSpec to copy all refs starting with refs/heads/* to refs/remotes/origin/*. This mimics how - // a typical repository looks - val refSpec = RefSpec("refs/heads/*:refs/remotes/origin/*").setForceUpdate(true) - - remoteSetUrl() - .setRemoteName("origin") - .setRemoteUri(URIish(tempGitRemoteDir.toURI().toString())) - .call() - - push() - .setRefSpecs(refSpec) - .setRemote("origin") - .setPushAll() - .setPushTags() - .call() - - fetch().setRefSpecs(refSpec).call() -} - -private fun Git.addCommitsAndTags( - targetBranch: GitRef.Branch, - tempRepoDir: File, -) { - for ((modifier, i) in (1..3).withIndex()) { - val tagName = - if (targetBranch == GitRef.Branch.DEVELOP) { - "v1.$modifier.0-beta.1" - } else { - "v1.$modifier.0" - } - - val tagMessage = "Tag $tagName" - - val file = File(tempRepoDir, "file$i.txt") - file.writeText("This is file $i.") - add().addFilepattern("file$i.txt").call() - - val author = PersonIdent("Author $i", "author$i@example.com") - val committer = PersonIdent("Committer $i", "committer$i@example.com") - - val commit: RevCommit = - commit().apply { - this.author = author - this.committer = committer - this.message = "Commit $i" - }.call() - - tag().apply { - this.name = tagName - this.tagger = tagger - this.message = tagMessage - this.objectId = commit - }.call() - } -} diff --git a/src/test/kotlin/com/figure/gradle/semver/util/GradleArgs.kt b/src/test/kotlin/com/figure/gradle/semver/util/GradleArgs.kt deleted file mode 100644 index 4708a14..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/util/GradleArgs.kt +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.util - -object GradleArgs { - const val PARALLEL = "--parallel" - const val BUILD_CACHE = "--build-cache" - const val CONFIGURATION_CACHE = "--configuration-cache" - const val STACKTRACE = "--stacktrace" -} diff --git a/src/test/kotlin/com/figure/gradle/semver/util/GradleRunnerUtils.kt b/src/test/kotlin/com/figure/gradle/semver/util/GradleRunnerUtils.kt deleted file mode 100644 index 31f327d..0000000 --- a/src/test/kotlin/com/figure/gradle/semver/util/GradleRunnerUtils.kt +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2024 Figure Technologies and its affiliates. - * - * This source code is licensed under the Apache 2.0 license found in the - * LICENSE.md file in the root directory of this source tree. - */ - -package com.figure.gradle.semver.util - -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner - -fun GradleRunner.runTask(arguments: List): Pair { - val firstRun = - this - .withArguments(arguments) - .build() - - val secondRun = - this - .withArguments(arguments) - .build() - - return firstRun to secondRun -} diff --git a/src/test/resources/integration/basic-project/build.gradle.kts b/src/test/resources/integration/basic-project/build.gradle.kts deleted file mode 100644 index 35ac6e7..0000000 --- a/src/test/resources/integration/basic-project/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -import org.gradle.kotlin.dsl.repositories - -plugins { - kotlin("jvm") version "@kotlin-version@" - id("com.figure.gradle.semver-plugin") -} - -repositories { - mavenCentral() -} - -semver { - overrideVersion("9.9.9") -} - -version = semver.version diff --git a/src/test/resources/integration/basic-project/settings.gradle.kts b/src/test/resources/integration/basic-project/settings.gradle.kts deleted file mode 100644 index 7ec2783..0000000 --- a/src/test/resources/integration/basic-project/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "integration-test-project" diff --git a/src/test/resources/integration/standard-project/build.gradle.kts b/src/test/resources/integration/standard-project/build.gradle.kts deleted file mode 100644 index adf9113..0000000 --- a/src/test/resources/integration/standard-project/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - kotlin("jvm") version "@kotlin-version@" - id("com.figure.gradle.semver-plugin") -} - -repositories { - mavenCentral() -} - -semver { - tagPrefix("v") - initialVersion("0.0.1") - - findProperty("semver.overrideVersion")?.toString() - ?.let { overrideVersion(it) } - - val semVerModifier = findProperty("semver.modifier")?.toString() - ?.let { buildVersionModifier(it) } - ?: { nextPatch() } - - versionModifier(semVerModifier) -} - -version = semver.version diff --git a/src/test/resources/integration/standard-project/settings.gradle.kts b/src/test/resources/integration/standard-project/settings.gradle.kts deleted file mode 100644 index 7ec2783..0000000 --- a/src/test/resources/integration/standard-project/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "integration-test-project"