From edf9e3c8c79b44bb63a4b39a73170065e73aa555 Mon Sep 17 00:00:00 2001 From: daz Date: Thu, 23 Jan 2025 14:07:12 -0700 Subject: [PATCH] Improve comparison of Gradle versions --- sources/src/execution/gradle.ts | 68 +++++++++++--- sources/test/jest/gradle-version.test.ts | 115 ++++++++++++++++------- 2 files changed, 139 insertions(+), 44 deletions(-) diff --git a/sources/src/execution/gradle.ts b/sources/src/execution/gradle.ts index f8237c6..86f72db 100644 --- a/sources/src/execution/gradle.ts +++ b/sources/src/execution/gradle.ts @@ -35,18 +35,45 @@ async function executeGradleBuild(executable: string | undefined, root: string, } export function versionIsAtLeast(actualVersion: string, requiredVersion: string): boolean { - const splitVersion = actualVersion.split('-') - const coreVersion = splitVersion[0] - const prerelease = splitVersion.length > 1 - - const actualSemver = semver.coerce(coreVersion)! - const comparisonSemver = semver.coerce(requiredVersion)! - - if (prerelease) { - return semver.gt(actualSemver, comparisonSemver) - } else { - return semver.gte(actualSemver, comparisonSemver) + if (actualVersion === requiredVersion) { + return true } + + const actual = new GradleVersion(actualVersion) + const required = new GradleVersion(requiredVersion) + + const actualSemver = semver.coerce(actual.versionPart)! + const comparisonSemver = semver.coerce(required.versionPart)! + + if (semver.gt(actualSemver, comparisonSemver)) { + return true // Actual version is greater than comparison. So it's at least as new. + } + if (semver.lt(actualSemver, comparisonSemver)) { + return false // Actual version is less than comparison. So it's not as new. + } + + // Actual and required version numbers are equal, so compare the other parts + + if (actual.snapshotPart || required.snapshotPart) { + if (actual.snapshotPart && !required.snapshotPart && !required.stagePart) { + return false // Actual has a snapshot, but required is a plain version. Required is newer. + } + if (required.snapshotPart && !actual.snapshotPart && !actual.stagePart) { + return true // Required has a snapshot, but actual is a plain version. Actual is newer. + } + + return false // Cannot compare case where both versions have a snapshot or stage + } + + if (actual.stagePart) { + if (required.stagePart) { + return actual.stagePart >= required.stagePart // Compare stages for newer + } + + return false // Actual has a stage, but required does not. So required is always newer. + } + + return true // Actual has no stage part or snapshot part, so it cannot be older than required. } export async function findGradleVersionOnPath(): Promise { @@ -72,3 +99,22 @@ class GradleExecutable { readonly executable: string ) {} } + +class GradleVersion { + static PATTERN = /((\d+)(\.\d+)+)(-([a-z]+)-(\w+))?(-(SNAPSHOT|\d{14}([-+]\d{4})?))?/ + + versionPart: string + stagePart: string + snapshotPart: string + + constructor(readonly version: string) { + const matcher = GradleVersion.PATTERN.exec(version) + if (!matcher) { + throw new Error(`'${version}' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')`) + } + + this.versionPart = matcher[1] + this.stagePart = matcher[4] + this.snapshotPart = matcher[7] + } +} diff --git a/sources/test/jest/gradle-version.test.ts b/sources/test/jest/gradle-version.test.ts index 9bcf520..16f664c 100644 --- a/sources/test/jest/gradle-version.test.ts +++ b/sources/test/jest/gradle-version.test.ts @@ -3,43 +3,92 @@ import {describe, expect, it} from '@jest/globals' import {versionIsAtLeast, parseGradleVersionFromOutput} from '../../src/execution/gradle' describe('gradle', () => { - describe('can compare version with', () => { - it('same version', async () => { - expect(versionIsAtLeast('6.7.1', '6.7.1')).toBe(true) - expect(versionIsAtLeast('7.0', '7.0')).toBe(true) - expect(versionIsAtLeast('7.0', '7.0.0')).toBe(true) - }) - it('newer version', async () => { - expect(versionIsAtLeast('6.7.1', '6.7.2')).toBe(false) - expect(versionIsAtLeast('7.0', '8.0')).toBe(false) - expect(versionIsAtLeast('7.0', '7.0.1')).toBe(false) - }) - it('older version', async () => { - expect(versionIsAtLeast('6.7.2', '6.7.1')).toBe(true) - expect(versionIsAtLeast('8.0', '7.0')).toBe(true) - expect(versionIsAtLeast('7.0.1', '7.0')).toBe(true) - }) - it('rc version', async () => { - expect(versionIsAtLeast('8.0.2-rc-1', '8.0.1')).toBe(true) - expect(versionIsAtLeast('8.0.2-rc-1', '8.0.2')).toBe(false) - expect(versionIsAtLeast('8.1-rc-1', '8.0')).toBe(true) - expect(versionIsAtLeast('8.0-rc-1', '8.0')).toBe(false) - }) - it('snapshot version', async () => { - expect(versionIsAtLeast('8.11-20240829002031+0000', '8.10')).toBe(true) - expect(versionIsAtLeast('8.11-20240829002031+0000', '8.10.1')).toBe(true) - expect(versionIsAtLeast('8.11-20240829002031+0000', '8.11')).toBe(false) + describe('can compare versions that are', () => { + function versionsAreOrdered(versions: string[]): void { + for (let i = 0; i < versions.length; i++) { + // Compare with all other versions + for (let j = 0; j < versions.length; j++) { + if (i >= j) { + it(`${versions[i]} is at least ${versions[j]}`, () => { + expect(versionIsAtLeast(versions[i], versions[j])).toBe(true) + }) + } else { + it(`${versions[i]} is NOT at least ${versions[j]}`, () => { + expect(versionIsAtLeast(versions[i], versions[j])).toBe(false) + }) + } + } + } + } - expect(versionIsAtLeast('8.10.2-20240828012138+0000', '8.10')).toBe(true) - expect(versionIsAtLeast('8.10.2-20240828012138+0000', '8.10.1')).toBe(true) - expect(versionIsAtLeast('8.10.2-20240828012138+0000', '8.10.2')).toBe(false) - expect(versionIsAtLeast('8.10.2-20240828012138+0000', '8.11')).toBe(false) + function versionsAreNotOrdered(versions: string[]): void { + for (let i = 0; i < versions.length; i++) { + // Compare with all other versions + for (let j = 0; j < versions.length; j++) { + if (i !== j) { + it(`${versions[i]} is NOT at least ${versions[j]}`, () => { + expect(versionIsAtLeast(versions[i], versions[j])).toBe(false) + }) + } + } + } + } - expect(versionIsAtLeast('9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.0')).toBe(true) - expect(versionIsAtLeast('9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.0.1')).toBe(true) - expect(versionIsAtLeast('9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.1')).toBe(false) + function versionsAreEqual(versions: string[]): void { + for (let i = 0; i < versions.length; i++) { + // Compare with all other versions + for (let j = 0; j < versions.length; j++) { + it(`${versions[i]} is at least ${versions[j]}`, () => { + expect(versionIsAtLeast(versions[i], versions[j])).toBe(true) + }) + } + } + } + + describe('simple versions', () => { + versionsAreOrdered(['6.0', '6.7', '6.7.1', '6.7.2', '7.0', '7.0.1', '7.1', '8.0', '8.12.1']) + + versionsAreEqual(['7.0', '7.0.0']) + versionsAreEqual(['7.1', '7.1.0']) + }) + + describe('rc versions', () => { + versionsAreOrdered([ + '8.10', '8.11-rc-1', '8.11-rc-2', '8.11', '8.11.1-rc-1', '8.11.1' + ]) + }) + + describe('milestone versions', () => { + versionsAreOrdered([ + '8.12.1', '8.12.2-milestone-1', '8.12.2', '8.13-milestone-1', '8.13-milestone-2', '8.13' + ]) + versionsAreOrdered([ + '8.12.1', '8.12.2-milestone-1', '8.12.2-milestone-2', '8.12.2-rc-1', '8.12.2' + ]) + }) + + describe('preview versions', () => { + versionsAreOrdered([ + '8.12.1', '8.12.2-preview-1', '8.12.2', '8.13-preview-1', '8.13-preview-2', '8.13' + ]) + versionsAreOrdered([ + '8.12.1', '8.12.2-milestone-1', '8.12.2-preview-1', '8.12.2-rc-1', '8.12.2' + ]) + }) + + describe('snapshot versions', () => { + versionsAreOrdered([ + '8.10.1', '8.10.2-20240828012138+0000', '8.10.2', '8.11-20240829002031+0000', '8.11' + ]) + versionsAreOrdered([ + '9.0', '9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.1' + ]) + versionsAreNotOrdered([ + '8.10.2-20240828012138+0000', '8.10.2-20240828010000+1000', '8.10.2-milestone-1' + ]) }) }) + describe('can parse version from output', () => { it('major version', async () => { const output = `