diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c689801..5571320 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -398,6 +398,11 @@ jobs: dotnet_8_version: ${{ steps.versions.outputs.dotnet_8_version }} dotnet_9_version: ${{ steps.versions.outputs.dotnet_9_version }} dotnet_10_version: ${{ steps.versions.outputs.dotnet_10_version }} + golang_version: ${{ steps.versions.outputs.golang_version }} + maven_version: ${{ steps.versions.outputs.maven_version }} + gradle_version: ${{ steps.versions.outputs.gradle_version }} + jdtls_url: ${{ steps.versions.outputs.jdtls_url }} + plantuml_url: ${{ steps.versions.outputs.plantuml_url }} steps: - name: Set lowercase repository name id: repo @@ -424,6 +429,26 @@ jobs: CSHARP_LS_VERSION=$(curl -s https://api.nuget.org/v3-flatcontainer/csharp-ls/index.json | jq -r '.versions[-1]') echo "csharp_ls_version=$CSHARP_LS_VERSION" >> $GITHUB_OUTPUT + # Toolchains pinned per-build so the resolved version is the layer cache + # key: passing it as a build-arg busts the image cache when it changes. + # The snippets resolve these themselves when the arg is empty (local dev + # builds), but CI must resolve here or the registry layer cache freezes + # the version. Go strips the "go" prefix; the snippet re-adds it. + GOLANG_VERSION=$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -1) + echo "golang_version=${GOLANG_VERSION#go}" >> $GITHUB_OUTPUT + + MAVEN_VERSION=$(curl -fsSL https://dlcdn.apache.org/maven/maven-3/ | grep -oE '3\.[0-9]+\.[0-9]+/' | tr -d / | sort -uV | tail -1) + echo "maven_version=$MAVEN_VERSION" >> $GITHUB_OUTPUT + + GRADLE_VERSION=$(curl -fsSL https://services.gradle.org/versions/current | jq -r '.version') + echo "gradle_version=$GRADLE_VERSION" >> $GITHUB_OUTPUT + + JDTLS_FILE=$(curl -fsSL https://download.eclipse.org/jdtls/snapshots/latest.txt) + echo "jdtls_url=https://download.eclipse.org/jdtls/snapshots/${JDTLS_FILE}" >> $GITHUB_OUTPUT + + PLANTUML_TAG=$(curl -fsSL https://api.github.com/repos/plantuml/plantuml/releases/latest | jq -r '.tag_name') + echo "plantuml_url=https://github.com/plantuml/plantuml/releases/download/${PLANTUML_TAG}/plantuml-${PLANTUML_TAG#v}.jar" >> $GITHUB_OUTPUT + # Build all container images in parallel (each builds independently from node:20-slim) build-images: name: Build ${{ matrix.image }} @@ -438,37 +463,71 @@ jobs: tags_extra: | ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:latest ghcr.io/${{ needs.prepare-versions.outputs.repo_name }}:copilot-latest - build_args: "COPILOT_VERSION=$COPILOT_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION - image: rust dockerfile: ./docker/generated/Dockerfile.rust - build_args: "COPILOT_VERSION=$COPILOT_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION - image: golang dockerfile: ./docker/generated/Dockerfile.golang - build_args: "COPILOT_VERSION=$COPILOT_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + GOLANG_VERSION=$GOLANG_VERSION - image: playwright dockerfile: ./docker/generated/Dockerfile.playwright - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nPLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION - image: dotnet-8 dockerfile: ./docker/generated/Dockerfile.dotnet-8 - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_8_VERSION=$DOTNET_8_VERSION - image: dotnet-9 dockerfile: ./docker/generated/Dockerfile.dotnet-9 - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_9_VERSION=$DOTNET_9_VERSION - image: dotnet-10 dockerfile: ./docker/generated/Dockerfile.dotnet-10 - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_10_VERSION=$DOTNET_10_VERSION + CSHARP_LS_VERSION=$CSHARP_LS_VERSION - image: dotnet dockerfile: ./docker/generated/Dockerfile.dotnet - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_8_VERSION=$DOTNET_8_VERSION + DOTNET_SDK_9_VERSION=$DOTNET_9_VERSION + DOTNET_SDK_10_VERSION=$DOTNET_10_VERSION + CSHARP_LS_VERSION=$CSHARP_LS_VERSION - image: dotnet-playwright dockerfile: ./docker/generated/Dockerfile.dotnet-playwright - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nPLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_8_VERSION=$DOTNET_8_VERSION + DOTNET_SDK_9_VERSION=$DOTNET_9_VERSION + DOTNET_SDK_10_VERSION=$DOTNET_10_VERSION + PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION + CSHARP_LS_VERSION=$CSHARP_LS_VERSION - image: dotnet-rust dockerfile: ./docker/generated/Dockerfile.dotnet-rust - build_args: "COPILOT_VERSION=$COPILOT_VERSION\nDOTNET_SDK_8_VERSION=$DOTNET_8_VERSION\nDOTNET_SDK_9_VERSION=$DOTNET_9_VERSION\nDOTNET_SDK_10_VERSION=$DOTNET_10_VERSION\nCSHARP_LS_VERSION=$CSHARP_LS_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + DOTNET_SDK_8_VERSION=$DOTNET_8_VERSION + DOTNET_SDK_9_VERSION=$DOTNET_9_VERSION + DOTNET_SDK_10_VERSION=$DOTNET_10_VERSION + CSHARP_LS_VERSION=$CSHARP_LS_VERSION - image: java dockerfile: ./docker/generated/Dockerfile.java - build_args: "COPILOT_VERSION=$COPILOT_VERSION" + build_args: | + COPILOT_VERSION=$COPILOT_VERSION + MAVEN_VERSION=$MAVEN_VERSION + GRADLE_VERSION=$GRADLE_VERSION + JDTLS_URL=$JDTLS_URL + PLANTUML_URL=$PLANTUML_URL steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -496,6 +555,11 @@ jobs: ARGS="${ARGS//\$DOTNET_9_VERSION/${{ needs.prepare-versions.outputs.dotnet_9_version }}}" ARGS="${ARGS//\$DOTNET_10_VERSION/${{ needs.prepare-versions.outputs.dotnet_10_version }}}" ARGS="${ARGS//\$CSHARP_LS_VERSION/${{ needs.prepare-versions.outputs.csharp_ls_version }}}" + ARGS="${ARGS//\$GOLANG_VERSION/${{ needs.prepare-versions.outputs.golang_version }}}" + ARGS="${ARGS//\$MAVEN_VERSION/${{ needs.prepare-versions.outputs.maven_version }}}" + ARGS="${ARGS//\$GRADLE_VERSION/${{ needs.prepare-versions.outputs.gradle_version }}}" + ARGS="${ARGS//\$JDTLS_URL/${{ needs.prepare-versions.outputs.jdtls_url }}}" + ARGS="${ARGS//\$PLANTUML_URL/${{ needs.prepare-versions.outputs.plantuml_url }}}" echo "build_args<> $GITHUB_OUTPUT echo -e "$ARGS" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT diff --git a/docker/generated/Dockerfile.golang b/docker/generated/Dockerfile.golang index 81c885f..4949beb 100644 --- a/docker/generated/Dockerfile.golang +++ b/docker/generated/Dockerfile.golang @@ -76,12 +76,17 @@ RUN ARCH=$(dpkg --print-architecture) \ # --- snippet: golang --- # Install Go # Using official Go installation method -ENV GOLANG_VERSION=1.26.3 ENV GOPATH=/usr/local/go-workspace ENV PATH="/usr/local/go/bin:${GOPATH}/bin:${PATH}" -# Install Go from official tarball -RUN curl -fsSL "https://go.dev/dl/go${GOLANG_VERSION}.linux-$(dpkg --print-architecture).tar.gz" -o go.tar.gz \ +# Resolve the latest stable Go at image-build time (go.dev/VERSION returns the +# current release, e.g. "go1.26.4"). Strip any leading "go" and re-prepend it so +# GOLANG_VERSION can be pinned as either "1.26.3" or "go1.26.3". +ARG GOLANG_VERSION +RUN ver="${GOLANG_VERSION:-$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -1)}" \ + && test -n "$ver" \ + && ver="${ver#go}" \ + && curl -fsSL "https://go.dev/dl/go${ver}.linux-$(dpkg --print-architecture).tar.gz" -o go.tar.gz \ && tar -C /usr/local -xzf go.tar.gz \ && rm go.tar.gz diff --git a/docker/generated/Dockerfile.java b/docker/generated/Dockerfile.java index 5a39ca2..449304f 100644 --- a/docker/generated/Dockerfile.java +++ b/docker/generated/Dockerfile.java @@ -89,23 +89,33 @@ ENV JAVA_HOME=/usr/lib/jvm/temurin-25-jdk ENV PATH="${JAVA_HOME}/bin:${PATH}" -# Install Maven -ARG MAVEN_VERSION=3.9.9 -RUN curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o maven.tar.gz \ +# Install Maven. Resolve the latest stable 3.9.x from the dlcdn mirror listing +# (the maven-metadata tag points at Maven 4 RCs, which we don't want), +# but download from archive.apache.org, which keeps every historical release — +# dlcdn prunes old versions, so a pinned older MAVEN_VERSION would 404 there. +# Set MAVEN_VERSION to pin a specific 3.x release. +ARG MAVEN_VERSION +RUN ver="${MAVEN_VERSION:-$(curl -fsSL https://dlcdn.apache.org/maven/maven-3/ | grep -oE '3\.[0-9]+\.[0-9]+/' | tr -d / | sort -uV | tail -1)}" \ + && test -n "$ver" \ + && curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${ver}/binaries/apache-maven-${ver}-bin.tar.gz" -o maven.tar.gz \ && tar -C /usr/local -xzf maven.tar.gz \ - && ln -s /usr/local/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn \ + && ln -s "/usr/local/apache-maven-${ver}/bin/mvn" /usr/local/bin/mvn \ && rm maven.tar.gz -# Install Gradle -ARG GRADLE_VERSION=8.12 -RUN curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" -o gradle.zip \ +# Install Gradle. Resolve the current release at build time from the Gradle +# version API. Set GRADLE_VERSION to pin a specific release. +ARG GRADLE_VERSION +RUN ver="${GRADLE_VERSION:-$(curl -fsSL https://services.gradle.org/versions/current | grep -oP '"version"\s*:\s*"\K[^"]+')}" \ + && test -n "$ver" \ + && curl -fsSL "https://services.gradle.org/distributions/gradle-${ver}-bin.zip" -o gradle.zip \ && unzip -d /usr/local gradle.zip \ - && ln -s /usr/local/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle \ + && ln -s "/usr/local/gradle-${ver}/bin/gradle" /usr/local/bin/gradle \ && rm gradle.zip -# Install PlantUML -ARG PLANTUML_VERSION=1.2025.2 -RUN curl -fsSL "https://github.com/plantuml/plantuml/releases/download/v${PLANTUML_VERSION}/plantuml-${PLANTUML_VERSION}.jar" -o /usr/local/lib/plantuml.jar \ +# Install PlantUML. The GitHub "latest release" exposes a stable unversioned +# asset URL, and the jar lands at a version-free path. Set PLANTUML_URL to pin. +ARG PLANTUML_URL=https://github.com/plantuml/plantuml/releases/latest/download/plantuml.jar +RUN curl -fsSL "${PLANTUML_URL}" -o /usr/local/lib/plantuml.jar \ && echo '#!/bin/sh\nexec java -jar /usr/local/lib/plantuml.jar "$@"' > /usr/local/bin/plantuml \ && chmod +x /usr/local/bin/plantuml @@ -133,10 +143,13 @@ # --- snippet: lsp-java --- # Install Eclipse JDT Language Server for Java code intelligence -ARG JDTLS_VERSION=1.43.0 -ARG JDTLS_TIMESTAMP=202412191447 +# Resolve the latest build at image-build time. Eclipse only retains the most +# recent milestone, so any pinned milestone URL eventually 404s; the snapshots +# `-latest.tar.gz` symlink always points at the current build. Set JDTLS_URL to +# a specific versioned tarball to pin. +ARG JDTLS_URL=https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz RUN mkdir -p /usr/local/share/jdtls \ - && curl -fsSL "https://download.eclipse.org/jdtls/milestones/${JDTLS_VERSION}/jdt-language-server-${JDTLS_VERSION}-${JDTLS_TIMESTAMP}.tar.gz" -o jdtls.tar.gz \ + && curl -fsSL "${JDTLS_URL}" -o jdtls.tar.gz \ && tar -C /usr/local/share/jdtls -xzf jdtls.tar.gz \ && rm jdtls.tar.gz diff --git a/docker/snippets/golang.Dockerfile b/docker/snippets/golang.Dockerfile index 0442150..ceb49a8 100644 --- a/docker/snippets/golang.Dockerfile +++ b/docker/snippets/golang.Dockerfile @@ -1,11 +1,16 @@ # Install Go # Using official Go installation method -ENV GOLANG_VERSION=1.26.3 ENV GOPATH=/usr/local/go-workspace ENV PATH="/usr/local/go/bin:${GOPATH}/bin:${PATH}" -# Install Go from official tarball -RUN curl -fsSL "https://go.dev/dl/go${GOLANG_VERSION}.linux-$(dpkg --print-architecture).tar.gz" -o go.tar.gz \ +# Resolve the latest stable Go at image-build time (go.dev/VERSION returns the +# current release, e.g. "go1.26.4"). Strip any leading "go" and re-prepend it so +# GOLANG_VERSION can be pinned as either "1.26.3" or "go1.26.3". +ARG GOLANG_VERSION +RUN ver="${GOLANG_VERSION:-$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -1)}" \ + && test -n "$ver" \ + && ver="${ver#go}" \ + && curl -fsSL "https://go.dev/dl/go${ver}.linux-$(dpkg --print-architecture).tar.gz" -o go.tar.gz \ && tar -C /usr/local -xzf go.tar.gz \ && rm go.tar.gz diff --git a/docker/snippets/java.Dockerfile b/docker/snippets/java.Dockerfile index ae8e0da..159b72c 100644 --- a/docker/snippets/java.Dockerfile +++ b/docker/snippets/java.Dockerfile @@ -13,23 +13,33 @@ RUN ln -s /usr/lib/jvm/temurin-25-jdk-$(dpkg --print-architecture) /usr/lib/jvm/ ENV JAVA_HOME=/usr/lib/jvm/temurin-25-jdk ENV PATH="${JAVA_HOME}/bin:${PATH}" -# Install Maven -ARG MAVEN_VERSION=3.9.9 -RUN curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o maven.tar.gz \ +# Install Maven. Resolve the latest stable 3.9.x from the dlcdn mirror listing +# (the maven-metadata tag points at Maven 4 RCs, which we don't want), +# but download from archive.apache.org, which keeps every historical release — +# dlcdn prunes old versions, so a pinned older MAVEN_VERSION would 404 there. +# Set MAVEN_VERSION to pin a specific 3.x release. +ARG MAVEN_VERSION +RUN ver="${MAVEN_VERSION:-$(curl -fsSL https://dlcdn.apache.org/maven/maven-3/ | grep -oE '3\.[0-9]+\.[0-9]+/' | tr -d / | sort -uV | tail -1)}" \ + && test -n "$ver" \ + && curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${ver}/binaries/apache-maven-${ver}-bin.tar.gz" -o maven.tar.gz \ && tar -C /usr/local -xzf maven.tar.gz \ - && ln -s /usr/local/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn \ + && ln -s "/usr/local/apache-maven-${ver}/bin/mvn" /usr/local/bin/mvn \ && rm maven.tar.gz -# Install Gradle -ARG GRADLE_VERSION=8.12 -RUN curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" -o gradle.zip \ +# Install Gradle. Resolve the current release at build time from the Gradle +# version API. Set GRADLE_VERSION to pin a specific release. +ARG GRADLE_VERSION +RUN ver="${GRADLE_VERSION:-$(curl -fsSL https://services.gradle.org/versions/current | grep -oP '"version"\s*:\s*"\K[^"]+')}" \ + && test -n "$ver" \ + && curl -fsSL "https://services.gradle.org/distributions/gradle-${ver}-bin.zip" -o gradle.zip \ && unzip -d /usr/local gradle.zip \ - && ln -s /usr/local/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle \ + && ln -s "/usr/local/gradle-${ver}/bin/gradle" /usr/local/bin/gradle \ && rm gradle.zip -# Install PlantUML -ARG PLANTUML_VERSION=1.2025.2 -RUN curl -fsSL "https://github.com/plantuml/plantuml/releases/download/v${PLANTUML_VERSION}/plantuml-${PLANTUML_VERSION}.jar" -o /usr/local/lib/plantuml.jar \ +# Install PlantUML. The GitHub "latest release" exposes a stable unversioned +# asset URL, and the jar lands at a version-free path. Set PLANTUML_URL to pin. +ARG PLANTUML_URL=https://github.com/plantuml/plantuml/releases/latest/download/plantuml.jar +RUN curl -fsSL "${PLANTUML_URL}" -o /usr/local/lib/plantuml.jar \ && echo '#!/bin/sh\nexec java -jar /usr/local/lib/plantuml.jar "$@"' > /usr/local/bin/plantuml \ && chmod +x /usr/local/bin/plantuml diff --git a/docker/snippets/lsp-java.Dockerfile b/docker/snippets/lsp-java.Dockerfile index 8b1f08a..386e671 100644 --- a/docker/snippets/lsp-java.Dockerfile +++ b/docker/snippets/lsp-java.Dockerfile @@ -1,8 +1,11 @@ # Install Eclipse JDT Language Server for Java code intelligence -ARG JDTLS_VERSION=1.43.0 -ARG JDTLS_TIMESTAMP=202412191447 +# Resolve the latest build at image-build time. Eclipse only retains the most +# recent milestone, so any pinned milestone URL eventually 404s; the snapshots +# `-latest.tar.gz` symlink always points at the current build. Set JDTLS_URL to +# a specific versioned tarball to pin. +ARG JDTLS_URL=https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz RUN mkdir -p /usr/local/share/jdtls \ - && curl -fsSL "https://download.eclipse.org/jdtls/milestones/${JDTLS_VERSION}/jdt-language-server-${JDTLS_VERSION}-${JDTLS_TIMESTAMP}.tar.gz" -o jdtls.tar.gz \ + && curl -fsSL "${JDTLS_URL}" -o jdtls.tar.gz \ && tar -C /usr/local/share/jdtls -xzf jdtls.tar.gz \ && rm jdtls.tar.gz