From d5e90ad6466f82108020bc64d82a12055b98e8e4 Mon Sep 17 00:00:00 2001 From: tfenne Date: Fri, 1 May 2026 14:24:54 -0600 Subject: [PATCH 1/5] Fix release signing gate; bump next version shape; refresh Maven badge. The Sign task `onlyIf` and `signing.required` predicates referenced `gradle.taskGraph.hasTask("publishAllPublicationsToCentralPortal")`, but hasTask compares against `task.getPath()` which for a root-project task is `:publishAllPublicationsToCentralPortal` (with leading colon). The name-only string never matched, so signing was permanently disabled for every release task. Drop the redundant task-graph check entirely; `-Drelease=true` already gates the release flow via the pre-flight checks above (HEAD on a clean semver tag), so gating signing purely on `isRelease` is the right intent. Set `nextVersionBump = "x.x.x"` so snapshot builds compute 5.0.1 as the next planned release, ahead of an expected 5.0.x point release. Replace the dead `maven-badges.herokuapp.com` Maven Central badge (Heroku free tier shut down in late 2022) with an `img.shields.io` equivalent, and point the click-through at the current Sonatype Central artifact page instead of the defunct `search.maven.org` hash-fragment URL. --- README.md | 2 +- build.gradle | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4310b5498e..096745fa4e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build and Test](https://github.com/samtools/htsjdk/actions/workflows/tests.yml/badge.svg?branch=master&event=push)](https://github.com/samtools/htsjdk/actions/workflows/tests.yml) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.samtools/htsjdk/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.samtools%22%20AND%20a%3A%22htsjdk%22) +[![Maven Central](https://img.shields.io/maven-central/v/com.github.samtools/htsjdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/com.github.samtools/htsjdk) [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/samtools/htsjdk) [![Language](http://img.shields.io/badge/language-java-brightgreen.svg)](https://www.java.com/) ## A Java API for high-throughput sequencing data (HTS) formats. diff --git a/build.gradle b/build.gradle index 969ff850c4..9d0125a073 100644 --- a/build.gradle +++ b/build.gradle @@ -102,7 +102,7 @@ java { // // To change the planned bump (e.g. after a release lands), update nextVersionBump // below and commit. -final nextVersionBump = "x" +final nextVersionBump = "x.x.x" final isRelease = Boolean.getBoolean("release") final details = versionDetails() @@ -352,9 +352,9 @@ publishing { signing { useGpgCmd() sign(publishing.publications.htsjdk) - required = { isRelease && gradle.taskGraph.hasTask("publishAllPublicationsToCentralPortal") } + required = { isRelease } tasks.withType(Sign).configureEach { - onlyIf { isRelease && gradle.taskGraph.hasTask("publishAllPublicationsToCentralPortal") } + onlyIf { isRelease } } } From a2565fa142b33afa5ed1fa376c8958052ae63a5c Mon Sep 17 00:00:00 2001 From: tfenne Date: Fri, 1 May 2026 15:28:34 -0600 Subject: [PATCH 2/5] CI: drop runner-wide apt upgrade, cache samtools build, modernize actions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Install Samtools" step was running `apt-get upgrade` with no package list, which upgrades every package on the runner — including the firefox transitional `.deb` that bootstraps the Firefox snap. When the snap store is unreachable (an intermittent failure mode), the upgrade blocks for ~50 minutes and then fails the job, even though htsjdk needs nothing from firefox. Drop the upgrade entirely; the only packages this script needs are the three -dev libs already installed explicitly. Bump samtools to 1.23.1 and cache the built binary keyed on version + OS, so subsequent runs skip the ~3-5 min compile. Move the install prefix to /usr/local since /usr is reserved for the distro package manager and is the conventional location for locally-built software. Bump actions/checkout and actions/setup-java to v4 (v3 ran on Node 16, which is end-of-life on the Actions runtime), and switch the JDK distribution from `adopt` to its current name `temurin`. --- .github/workflows/tests.yml | 37 +++++++++++++++++++++---------------- scripts/install-samtools.sh | 9 ++++----- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f1a4eb7f6..9883aa1abb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,8 @@ on: jobs: test: env: - HTSJDK_SAMTOOLS_BIN: /usr/bin/samtools + HTSJDK_SAMTOOLS_BIN: /usr/local/bin/samtools + SAMTOOLS_VERSION: 1.23.1 runs-on: ubuntu-latest strategy: matrix: @@ -20,20 +21,27 @@ jobs: continue-on-error: ${{ matrix.experimental }} name: Java ${{ matrix.Java }} build and test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history + tags so palantir/git-version sees the latest release tag - name: Set up java ${{ matrix.Java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.Java }} - distribution: 'adopt' + distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Compile with Gradle run: ./gradlew compileJava + - name: Cache samtools + id: cache-samtools + uses: actions/cache@v4 + with: + path: /usr/local/bin/samtools + key: samtools-${{ env.SAMTOOLS_VERSION }}-${{ runner.os }} - name: Install Samtools + if: steps.cache-samtools.outputs.cache-hit != 'true' run: scripts/install-samtools.sh - name: Start the htsget server run: scripts/htsget-scripts/start-htsget-test-server.sh @@ -50,14 +58,14 @@ jobs: runs-on: ubuntu-latest name: Tests that require external APIs steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history + tags so palantir/git-version sees the latest release tag - name: Set up java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -75,14 +83,14 @@ jobs: runs-on: ubuntu-latest name: Java Format Check steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history + tags so palantir/git-version sees the latest release tag - name: Set up java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -92,14 +100,14 @@ jobs: runs-on: ubuntu-latest name: SpotBugs steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history + tags so palantir/git-version sees the latest release tag - name: Set up java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -111,6 +119,3 @@ jobs: with: name: spotBugs-Report path: build/reports/spotbugs - - - diff --git a/scripts/install-samtools.sh b/scripts/install-samtools.sh index b2bde8b987..ccf23f20c3 100755 --- a/scripts/install-samtools.sh +++ b/scripts/install-samtools.sh @@ -1,12 +1,11 @@ #!/bin/sh set -ex -#ubuntu specific +# ubuntu specific sudo apt-get update -sudo apt-get upgrade sudo apt-get install -y libncurses-dev libbz2-dev liblzma-dev -#install from the github tar -export SAMTOOLS_VERSION=1.21 +# install from the github tar +export SAMTOOLS_VERSION=1.23.1 wget https://github.com/samtools/samtools/releases/download/${SAMTOOLS_VERSION}/samtools-${SAMTOOLS_VERSION}.tar.bz2 tar -xjvf samtools-${SAMTOOLS_VERSION}.tar.bz2 -cd samtools-${SAMTOOLS_VERSION} && ./configure --prefix=/usr && make && sudo make install +cd samtools-${SAMTOOLS_VERSION} && ./configure --prefix=/usr/local && make && sudo make install From a5b7d15e58beb9a81c807b573bdf964e3abf6179 Mon Sep 17 00:00:00 2001 From: tfenne Date: Fri, 1 May 2026 15:35:29 -0600 Subject: [PATCH 3/5] CI: use gradle/actions/setup-gradle to cache the wrapper distribution. The previous setup relied on actions/setup-java's `cache: gradle` option, which caches `~/.gradle/caches` (resolved dependencies) but NOT `~/.gradle/wrapper/dists/`. Every CI run was therefore re-downloading the ~150 MB Gradle distribution zip on top of its own cold dependency resolution. Gradle publishes an official action that handles both: the wrapper distribution and the dependency cache, with smarter cache-key heuristics than setup-java's basic option. Drop `cache: gradle` from setup-java and add `gradle/actions/setup-gradle@v4` after it in each job. --- .github/workflows/tests.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9883aa1abb..90665b6037 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,8 @@ jobs: with: java-version: ${{ matrix.Java }} distribution: 'temurin' - cache: gradle + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Compile with Gradle @@ -66,7 +67,8 @@ jobs: with: java-version: '17' distribution: 'temurin' - cache: gradle + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Compile with Gradle @@ -91,7 +93,8 @@ jobs: with: java-version: '17' distribution: 'temurin' - cache: gradle + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Verify formatting @@ -108,7 +111,8 @@ jobs: with: java-version: '17' distribution: 'temurin' - cache: gradle + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Compile with Gradle From 2d86486d1ac933c21ad91d33116cf511fad62ad7 Mon Sep 17 00:00:00 2001 From: tfenne Date: Fri, 1 May 2026 15:44:50 -0600 Subject: [PATCH 4/5] Bump test JVM heap to 14G; pin gradle daemon footprint. We've been seeing intermittent `OutOfMemoryError: Java heap space` in CI test runs. With TestNG `parallel = "classes"` running availableProcessors() threads in a single JVM, peak heap demand scales with concurrency, and 12G was right at the edge on the 16G GitHub runner. Bump test JVM `maxHeapSize` to 14G. To make that safe, pin the gradle daemon's own footprint via gradle.properties (-Xmx512m + Metaspace cap) so it doesn't compete for the same physical RAM. Combined budget on a 16G runner: ~14G test heap + ~1G test non-heap + ~1G daemon + OS headroom. --- build.gradle | 2 +- gradle.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index 9d0125a073..bac4f1bee6 100644 --- a/build.gradle +++ b/build.gradle @@ -189,7 +189,7 @@ tasks.withType(Test).configureEach { task -> // set heap size for the test JVM(s) task.minHeapSize = "1G" - task.maxHeapSize = "12G" + task.maxHeapSize = "14G" task.jvmArgs '-Djava.awt.headless=true' //this prevents awt from displaying a java icon while the tests are running diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..9ac097894c --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx512m -XX:MaxMetaspaceSize=256m From 5575ba1f951a2ad2946fc5544882281689bfeaec Mon Sep 17 00:00:00 2001 From: tfenne Date: Fri, 1 May 2026 15:59:44 -0600 Subject: [PATCH 5/5] Make samtools version test a "at least" check. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous test asserted a substring match against a hardcoded version string, which broke any time CI bumped samtools (just happened: 1.21 → 1.23.1). htsjdk only cares that the local samtools is at least the version the tests were written against, not that it's exactly that version, so do a numeric semver comparison instead. Adds two small package-private helpers to SamtoolsTestUtils: - parseSamtoolsVersion(String) extracts the version from `samtools --version` output via a single regex. - compareVersions(a, b) compares two dotted-numeric version strings component-by-component, treating missing trailing components as zero. The version test now parses the running samtools version and asserts it is >= minimumSamtoolsVersion (renamed from expectedSamtoolsVersion to reflect the new semantics, bumped to 1.23.1 to match the version CI installs). Adds small unit tests for the parser and comparator covering typical output, two-component versions, missing version line, equality with implicit trailing zero, ordering across major/minor/patch, and the 1.10 vs 1.9 numeric-not-lexical case. --- .../java/htsjdk/utils/SamtoolsTestUtils.java | 35 +++++++++++++- .../htsjdk/utils/SamtoolsTestUtilsTest.java | 48 +++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/test/java/htsjdk/utils/SamtoolsTestUtils.java b/src/test/java/htsjdk/utils/SamtoolsTestUtils.java index 81b19af88b..1b59605e92 100644 --- a/src/test/java/htsjdk/utils/SamtoolsTestUtils.java +++ b/src/test/java/htsjdk/utils/SamtoolsTestUtils.java @@ -8,13 +8,46 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Test utilities for running samtools from htsjdk tests. */ public class SamtoolsTestUtils { private static final String SAMTOOLS_BINARY_ENV_VARIABLE = "HTSJDK_SAMTOOLS_BIN"; - public static final String expectedSamtoolsVersion = "1.21"; + public static final String minimumSamtoolsVersion = "1.23.1"; + + private static final Pattern SAMTOOLS_VERSION_PATTERN = Pattern.compile("(?m)^samtools\\s+(\\d+(?:\\.\\d+)*)"); + + /** + * Extracts the version string (e.g. "1.23.1") from the output of `samtools --version`, + * which begins with a line of the form {@code samtools }. + * + * @return the version string, or null if no version line was found. + */ + static String parseSamtoolsVersion(final String samtoolsVersionOutput) { + final Matcher m = SAMTOOLS_VERSION_PATTERN.matcher(samtoolsVersionOutput); + return m.find() ? m.group(1) : null; + } + + /** + * Compares two dotted-numeric version strings (e.g. "1.23.1") component-by-component. + * Missing trailing components are treated as zero, so "1.23" is equal to "1.23.0". + */ + static int compareVersions(final String a, final String b) { + final String[] aParts = a.split("\\."); + final String[] bParts = b.split("\\."); + final int len = Math.max(aParts.length, bParts.length); + for (int i = 0; i < len; i++) { + final int av = i < aParts.length ? Integer.parseInt(aParts[i]) : 0; + final int bv = i < bParts.length ? Integer.parseInt(bParts[i]) : 0; + if (av != bv) { + return Integer.compare(av, bv); + } + } + return 0; + } /** * @return true if samtools is available, otherwise false diff --git a/src/test/java/htsjdk/utils/SamtoolsTestUtilsTest.java b/src/test/java/htsjdk/utils/SamtoolsTestUtilsTest.java index cdf95dd596..3db9ded230 100644 --- a/src/test/java/htsjdk/utils/SamtoolsTestUtilsTest.java +++ b/src/test/java/htsjdk/utils/SamtoolsTestUtilsTest.java @@ -27,10 +27,52 @@ public void testSamtoolsVersion() { if (!SamtoolsTestUtils.isSamtoolsAvailable()) { throw new SkipException("Samtools not available on local device"); } - // If this test runs, but fails because version validation fails, then the local samtools version is - // not the one expected by the htsjdk tests final ProcessExecutor.ExitStatusAndOutput processStatus = SamtoolsTestUtils.executeSamToolsCommand("--version"); - Assert.assertTrue(processStatus.stdout.contains(SamtoolsTestUtils.expectedSamtoolsVersion)); + final String localVersion = SamtoolsTestUtils.parseSamtoolsVersion(processStatus.stdout); + Assert.assertNotNull( + localVersion, + "Could not parse samtools version from `samtools --version` output: " + processStatus.stdout); + Assert.assertTrue( + SamtoolsTestUtils.compareVersions(localVersion, SamtoolsTestUtils.minimumSamtoolsVersion) >= 0, + "Local samtools version " + localVersion + " is older than the minimum required by htsjdk tests (" + + SamtoolsTestUtils.minimumSamtoolsVersion + ")"); + } + + @Test + public void testParseSamtoolsVersionFromTypicalOutput() { + final String stdout = "samtools 1.23.1\nUsing htslib 1.23.1\nCopyright (C) 2024 Genome Research Ltd.\n"; + Assert.assertEquals(SamtoolsTestUtils.parseSamtoolsVersion(stdout), "1.23.1"); + } + + @Test + public void testParseSamtoolsVersionTwoComponent() { + Assert.assertEquals(SamtoolsTestUtils.parseSamtoolsVersion("samtools 1.21\n"), "1.21"); + } + + @Test + public void testParseSamtoolsVersionReturnsNullWhenAbsent() { + Assert.assertNull(SamtoolsTestUtils.parseSamtoolsVersion("nothing recognizable here\n")); + } + + @Test + public void testCompareVersionsEqualWithImplicitZero() { + Assert.assertEquals(SamtoolsTestUtils.compareVersions("1.23", "1.23.0"), 0); + } + + @Test + public void testCompareVersionsPatchGreater() { + Assert.assertTrue(SamtoolsTestUtils.compareVersions("1.23.1", "1.23") > 0); + } + + @Test + public void testCompareVersionsMajorLess() { + Assert.assertTrue(SamtoolsTestUtils.compareVersions("1.21", "1.23.1") < 0); + } + + @Test + public void testCompareVersionsNumericNotLexical() { + // 1.10 is greater than 1.9 numerically, even though "1.10" sorts before "1.9" lexically. + Assert.assertTrue(SamtoolsTestUtils.compareVersions("1.10", "1.9") > 0); } @Test(expectedExceptions = RuntimeException.class)