From c8e4d3fdcc4e46e2f02f25f51f84e64310fa091c Mon Sep 17 00:00:00 2001 From: trumpyla Date: Tue, 28 Jul 2026 08:52:50 -0400 Subject: [PATCH 1/3] feat: add secure multi-version Valhalla support --- .github/workflows/audit.yml | 7 + .github/workflows/auto-update.yml | 25 ++- .github/workflows/bottles.yml | 218 +++++++++++++++++++++ .github/workflows/release.yml | 39 ++-- .github/workflows/update.yml | 30 ++- .github/workflows/validate.yml | 68 +++++-- ABOUT.md | 110 +++++++++++ Aliases/openjdk-valhalla | 1 + Casks/jdk26ea.rb | 2 - Formula/openjdk-valhalla@27.rb | 127 ++++++++++++ Formula/openjdk-valhalla@28.rb | 128 ++++++++++++ README.md | 155 +++++++++------ SECURITY.md | 249 +++++++++++------------- scripts/test.sh | 6 +- scripts/update.sh | 3 +- scripts/validate-bottle-artifact.rb | 114 +++++++++++ tests/bottle_artifact_validator_test.rb | 113 +++++++++++ tests/formula_contract.bats | 35 ++++ tests/workflow_security_test.rb | 136 +++++++++++++ 19 files changed, 1300 insertions(+), 266 deletions(-) create mode 100644 .github/workflows/bottles.yml create mode 100644 ABOUT.md create mode 120000 Aliases/openjdk-valhalla create mode 100644 Formula/openjdk-valhalla@27.rb create mode 100644 Formula/openjdk-valhalla@28.rb create mode 100644 scripts/validate-bottle-artifact.rb create mode 100644 tests/bottle_artifact_validator_test.rb create mode 100644 tests/formula_contract.bats create mode 100644 tests/workflow_security_test.rb diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index e15a040..12ed4ce 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -19,3 +19,10 @@ jobs: run: | brew style Casks/jdk26ea.rb || true ruby -c Casks/jdk26ea.rb + + - name: Audit formulas + run: | + brew style Formula/*.rb + for formula in Formula/*.rb; do + ruby -c "$formula" + done diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml index fc5afd5..0b00957 100644 --- a/.github/workflows/auto-update.yml +++ b/.github/workflows/auto-update.yml @@ -1,9 +1,7 @@ -name: Auto Update JDK 26 EA +name: Legacy JDK 26 EA update on: - schedule: - - cron: '0 6 * * *' # Daily at 6 AM UTC - workflow_dispatch: # Allow manual triggering + workflow_dispatch: permissions: contents: write @@ -43,7 +41,7 @@ jobs: fi echo "Latest build: $build" - echo "build=$build" >> $GITHUB_OUTPUT + echo "build=$build" >>"$GITHUB_OUTPUT" # Get current version from cask current_version=$(grep -oP 'version "\K[^"]+' Casks/jdk26ea.rb) @@ -55,13 +53,13 @@ jobs: fi echo "Current build: $current_build" - echo "current_build=$current_build" >> $GITHUB_OUTPUT + echo "current_build=$current_build" >>"$GITHUB_OUTPUT" if [ "$build" != "$current_build" ]; then - echo "needs_update=true" >> $GITHUB_OUTPUT + echo "needs_update=true" >>"$GITHUB_OUTPUT" echo "New version available!" else - echo "needs_update=false" >> $GITHUB_OUTPUT + echo "needs_update=false" >>"$GITHUB_OUTPUT" echo "Already up to date" fi @@ -70,7 +68,7 @@ jobs: id: checksums run: | set -euo pipefail - build=${{ steps.fetch_version.outputs.build }} + build="${{ steps.fetch_version.outputs.build }}" # Create cache directory mkdir -p /tmp/jdk-downloads @@ -115,8 +113,8 @@ jobs: - name: Update cask if: steps.fetch_version.outputs.needs_update == 'true' run: | - build=${{ steps.fetch_version.outputs.build }} - current_build=${{ steps.fetch_version.outputs.current_build }} + set -Eeuo pipefail + build="${{ steps.fetch_version.outputs.build }}" # Update Cask new_cask_version="26-ea+${build},${build}" @@ -135,8 +133,9 @@ jobs: - name: Update formula if: steps.fetch_version.outputs.needs_update == 'true' run: | - build=${{ steps.fetch_version.outputs.build }} - current_build=${{ steps.fetch_version.outputs.current_build }} + set -Eeuo pipefail + build="${{ steps.fetch_version.outputs.build }}" + current_build="${{ steps.fetch_version.outputs.current_build }}" # Update Formula sed -i "s/26-ea+${current_build}/26-ea+${build}/g" Formula/jdk26ea.rb diff --git a/.github/workflows/bottles.yml b/.github/workflows/bottles.yml new file mode 100644 index 0000000..6a109ea --- /dev/null +++ b/.github/workflows/bottles.yml @@ -0,0 +1,218 @@ +name: Build Valhalla bottle + +on: + workflow_dispatch: + inputs: + formula: + description: Versioned formula to build + required: true + type: choice + options: + - openjdk-valhalla@27 + - openjdk-valhalla@28 + +permissions: {} + +concurrency: + group: bottle-${{ inputs.formula }} + cancel-in-progress: false + +jobs: + build: + runs-on: macos-14 + permissions: + contents: read + env: + FORMULA: ${{ inputs.formula }} + TAP: artagon/valhalla + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b + + - name: Verify Apple Silicon runner + run: | + set -Eeuo pipefail + [[ "$(uname -m)" == "arm64" ]] || { + printf 'Expected an arm64 runner, got %s\n' "$(uname -m)" >&2 + exit 1 + } + + - name: Tap this checkout + run: | + set -Eeuo pipefail + brew untap "${TAP}" >/dev/null 2>&1 || true + brew tap --custom-remote "${TAP}" "${GITHUB_WORKSPACE}" + + - name: Resolve bottle metadata + id: metadata + run: | + set -Eeuo pipefail + [[ "${GITHUB_REF}" == "refs/heads/main" ]] || { + printf 'Bottle builds must be dispatched from main, got %s\n' "${GITHUB_REF}" >&2 + exit 1 + } + info_file="$(mktemp)" + trap 'rm -f -- "${info_file}"' EXIT + brew info --json=v2 "${TAP}/${FORMULA}" >"${info_file}" + version="$(ruby -rjson -e \ + 'puts JSON.parse(File.read(ARGV.fetch(0))).fetch("formulae").first.fetch("versions").fetch("stable")' \ + "${info_file}")" + [[ "${version}" =~ ^[0-9A-Za-z][0-9A-Za-z._+-]*$ ]] || { + printf 'Unsafe formula version: %s\n' "${version}" >&2 + exit 1 + } + [[ "${GITHUB_SHA}" =~ ^[0-9a-f]{40}$ ]] || { + printf 'Invalid GitHub commit SHA\n' >&2 + exit 1 + } + safe_formula="${FORMULA//@/-}" + tag="bottle-${safe_formula}-${version}-${GITHUB_SHA::12}" + { + printf 'tag=%s\n' "${tag}" + printf 'version=%s\n' "${version}" + } >>"${GITHUB_OUTPUT}" + + - name: Build from the pinned source + run: | + set -Eeuo pipefail + brew install --build-bottle --build-from-source "${TAP}/${FORMULA}" + + - name: Run formula smoke test + run: brew test "${TAP}/${FORMULA}" + + - name: Create bottle and metadata + env: + BOTTLE_TAG: ${{ steps.metadata.outputs.tag }} + run: | + set -Eeuo pipefail + root_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${BOTTLE_TAG}" + mkdir -p bottle-output + cd bottle-output + brew bottle --json --root-url="${root_url}" "${TAP}/${FORMULA}" + + - name: Validate bottle artifact + id: artifact + env: + BOTTLE_TAG: ${{ steps.metadata.outputs.tag }} + BOTTLE_VERSION: ${{ steps.metadata.outputs.version }} + run: | + set -Eeuo pipefail + root_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${BOTTLE_TAG}" + ruby scripts/validate-bottle-artifact.rb \ + --directory bottle-output \ + --formula "${FORMULA}" \ + --tap "${TAP}" \ + --version "${BOTTLE_VERSION}" \ + --git-revision "${GITHUB_SHA}" \ + --root-url "${root_url}" \ + --github-output "${GITHUB_OUTPUT}" + + - name: Upload bottle job artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: ${{ steps.metadata.outputs.tag }} + path: |- + ${{ steps.artifact.outputs.bottle_path }} + ${{ steps.artifact.outputs.json_path }} + if-no-files-found: error + retention-days: 7 + + outputs: + tag: ${{ steps.metadata.outputs.tag }} + version: ${{ steps.metadata.outputs.version }} + + publish: + needs: build + runs-on: macos-14 + permissions: + contents: write + pull-requests: write + env: + FORMULA: ${{ inputs.formula }} + TAP: artagon/valhalla + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b + + - name: Download bottle job artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: ${{ needs.build.outputs.tag }} + path: bottle-artifact + + - name: Validate bottle artifact + id: artifact + env: + BOTTLE_TAG: ${{ needs.build.outputs.tag }} + BOTTLE_VERSION: ${{ needs.build.outputs.version }} + run: | + set -Eeuo pipefail + root_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${BOTTLE_TAG}" + ruby scripts/validate-bottle-artifact.rb \ + --directory bottle-artifact \ + --formula "${FORMULA}" \ + --tap "${TAP}" \ + --version "${BOTTLE_VERSION}" \ + --git-revision "${GITHUB_SHA}" \ + --root-url "${root_url}" \ + --github-output "${GITHUB_OUTPUT}" + + - name: Publish bottle release + env: + GH_TOKEN: ${{ github.token }} + BOTTLE_PATH: ${{ steps.artifact.outputs.bottle_path }} + BOTTLE_TAG: ${{ needs.build.outputs.tag }} + BOTTLE_VERSION: ${{ needs.build.outputs.version }} + run: | + set -Eeuo pipefail + gh api \ + --method POST \ + "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f ref="refs/tags/${BOTTLE_TAG}" \ + -f sha="${GITHUB_SHA}" + gh release create "${BOTTLE_TAG}" "${BOTTLE_PATH}" \ + --target "${GITHUB_SHA}" \ + --verify-tag \ + --prerelease \ + --title "${FORMULA} ${BOTTLE_VERSION} bottle" \ + --notes "Artagon Homebrew bottle built from the source pinned by Formula/${FORMULA}.rb." + + - name: Merge bottle metadata into formula + env: + BOTTLE_JSON: ${{ steps.artifact.outputs.json_path }} + run: | + set -Eeuo pipefail + brew untap "${TAP}" >/dev/null 2>&1 || true + brew tap --custom-remote "${TAP}" "${GITHUB_WORKSPACE}" + tap_root="$(brew --repository "${TAP}")" + brew bottle --merge --write --no-commit "${BOTTLE_JSON}" + cp "${tap_root}/Formula/${FORMULA}.rb" "Formula/${FORMULA}.rb" + + - name: Create bottle block pull request + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c + with: + token: ${{ github.token }} + commit-message: >- + chore(formula): add ${{ inputs.formula }} + ${{ needs.build.outputs.version }} bottle + title: >- + chore(formula): add ${{ inputs.formula }} + ${{ needs.build.outputs.version }} bottle + body: | + Adds the Homebrew-generated bottle block for `${{ inputs.formula }}` + version `${{ needs.build.outputs.version }}`. + + The bottle was built from the formula's pinned source, smoke-tested, + and published in release `${{ needs.build.outputs.tag }}`. + branch: bottle/${{ needs.build.outputs.tag }} + delete-branch: true + add-paths: Formula/${{ inputs.formula }}.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d1e2bc..d1b2322 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 # Fetch all history for changelog + persist-credentials: false - name: Set up Homebrew if: runner.os == 'Linux' @@ -33,42 +34,42 @@ jobs: id: version run: | set -euo pipefail - VERSION=$(grep -oP 'version "\K[^"]+' Casks/jdk26ea.rb) - if [[ -z "$VERSION" ]]; then + version=$(grep -oP 'version "\K[^"]+' Casks/jdk26ea.rb) + if [[ -z "$version" ]]; then echo "Failed to extract version from Casks/jdk26ea.rb" exit 1 fi - echo "version=$VERSION" >> $GITHUB_OUTPUT + printf 'version=%s\n' "$version" >>"$GITHUB_OUTPUT" # Extract build number for changelog - BUILD=$(echo "$VERSION" | grep -oP '\+\K\d+') - if [[ -z "$BUILD" || ! "$BUILD" =~ ^[0-9]+$ ]]; then - echo "Failed to extract build number from version $VERSION" + build=$(echo "$version" | grep -oP '\+\K\d+') + if [[ -z "$build" || ! "$build" =~ ^[0-9]+$ ]]; then + echo "Failed to extract build number from version $version" exit 1 fi - echo "build=$BUILD" >> $GITHUB_OUTPUT + printf 'build=%s\n' "$build" >>"$GITHUB_OUTPUT" - name: Check if tag exists id: check_tag run: | if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >>"$GITHUB_OUTPUT" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >>"$GITHUB_OUTPUT" fi - name: Get previous version if: steps.check_tag.outputs.exists == 'false' id: prev_version run: | - PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT + prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + printf 'tag=%s\n' "$prev_tag" >>"$GITHUB_OUTPUT" - if [ -n "$PREV_TAG" ]; then - PREV_VERSION=$(echo "$PREV_TAG" | sed 's/^v//') - echo "version=$PREV_VERSION" >> $GITHUB_OUTPUT + if [[ -n "$prev_tag" ]]; then + prev_version="${prev_tag#v}" + printf 'version=%s\n' "$prev_version" >>"$GITHUB_OUTPUT" else - echo "version=none" >> $GITHUB_OUTPUT + echo "version=none" >>"$GITHUB_OUTPUT" fi - name: Generate changelog @@ -88,7 +89,7 @@ jobs: echo 'changelog<> $GITHUB_OUTPUT + } >>"$GITHUB_OUTPUT" - name: Update README with current version if: steps.check_tag.outputs.exists == 'false' @@ -155,12 +156,10 @@ jobs: if: steps.check_tag.outputs.exists == 'false' id: jdk_notes run: | - BUILD="${{ steps.version.outputs.build }}" - # Try to fetch release notes (if available) - NOTES_URL="https://jdk.java.net/26/" + notes_url="https://jdk.java.net/26/" - echo "notes_url=$NOTES_URL" >> $GITHUB_OUTPUT + printf 'notes_url=%s\n' "$notes_url" >>"$GITHUB_OUTPUT" - name: Create Release if: steps.check_tag.outputs.exists == 'false' diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 1d1c4a8..b2e525b 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -1,9 +1,7 @@ -name: Update JDK 26 EA +name: Legacy JDK 26 EA verified update on: workflow_dispatch: - schedule: - - cron: '0 0 * * *' # Daily at midnight UTC permissions: contents: write @@ -94,19 +92,19 @@ jobs: rm -f "$page_file" - cat <>"$GITHUB_OUTPUT" -version=$VERSION -build=$BUILD -cask_version=${VERSION},${BUILD} -url_mac_arm=$URL_MAC_ARM -url_mac_intel=$URL_MAC_INTEL -url_linux_arm=$URL_LINUX_ARM -url_linux_x64=$URL_LINUX_X64 -sha_mac_arm=$SHA_MAC_ARM -sha_mac_intel=$SHA_MAC_INTEL -sha_linux_arm=$SHA_LINUX_ARM -sha_linux_x64=$SHA_LINUX_X64 -EOF + { + printf 'version=%s\n' "$VERSION" + printf 'build=%s\n' "$BUILD" + printf 'cask_version=%s,%s\n' "$VERSION" "$BUILD" + printf 'url_mac_arm=%s\n' "$URL_MAC_ARM" + printf 'url_mac_intel=%s\n' "$URL_MAC_INTEL" + printf 'url_linux_arm=%s\n' "$URL_LINUX_ARM" + printf 'url_linux_x64=%s\n' "$URL_LINUX_X64" + printf 'sha_mac_arm=%s\n' "$SHA_MAC_ARM" + printf 'sha_mac_intel=%s\n' "$SHA_MAC_INTEL" + printf 'sha_linux_arm=%s\n' "$SHA_LINUX_ARM" + printf 'sha_linux_x64=%s\n' "$SHA_LINUX_X64" + } >>"$GITHUB_OUTPUT" - name: Update cask and formula files env: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 4ef4a73..025a6e5 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -5,15 +5,28 @@ on: branches: [main] pull_request: +permissions: + contents: read + jobs: validate-syntax: runs-on: macos-14 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b + - name: Install validation tools + run: brew install actionlint bats-core shellcheck + + - name: Lint workflows and shell scripts + run: | + actionlint .github/workflows/*.yml + shellcheck scripts/test.sh scripts/update.sh tests/formula_contract.bats + - name: Validate cask syntax run: | brew style Casks/jdk26ea.rb @@ -25,16 +38,28 @@ jobs: - name: Validate formula syntax run: | - brew style Formula/jdk26ea.rb - ruby -c Formula/jdk26ea.rb + brew style Formula/*.rb + for formula in Formula/*.rb; do + ruby -c "$formula" + done + + - name: Validate formula compatibility contract + run: bats tests/formula_contract.bats + + - name: Validate workflow security contracts + run: | + ruby tests/workflow_security_test.rb + ruby tests/bottle_artifact_validator_test.rb test-install-macos: strategy: matrix: - os: [macos-13, macos-14] + os: [macos-14, macos-15-intel] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b @@ -79,6 +104,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b @@ -115,7 +142,8 @@ jobs: - name: Test JAVA_HOME setup run: | - export JAVA_HOME="$(brew --prefix jdk26ea)" + JAVA_HOME="$(brew --prefix jdk26ea)" + export JAVA_HOME echo "JAVA_HOME=$JAVA_HOME" echo "Testing java compiler and runtime..." "$JAVA_HOME/bin/java" -version @@ -123,7 +151,8 @@ jobs: - name: Test basic Java functionality run: | - export JAVA_HOME="$(brew --prefix jdk26ea)" + JAVA_HOME="$(brew --prefix jdk26ea)" + export JAVA_HOME export PATH="$JAVA_HOME/bin:$PATH" # Create and compile a test program @@ -146,24 +175,29 @@ jobs: validation-status: name: Validation Status + if: ${{ always() }} runs-on: ubuntu-latest + permissions: + contents: read needs: - validate-syntax - test-install-macos - test-install-linux steps: - name: Confirm completion - run: echo "✅ All validation jobs completed successfully." - - - name: Publish Validate status for branch protection env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - SHA: ${{ github.event.pull_request.head.sha || github.sha }} + VALIDATE_SYNTAX_RESULT: ${{ needs.validate-syntax.result }} + TEST_INSTALL_MACOS_RESULT: ${{ needs.test-install-macos.result }} + TEST_INSTALL_LINUX_RESULT: ${{ needs.test-install-linux.result }} run: | - gh api \ - --method POST \ - "repos/${REPO}/statuses/${SHA}" \ - -f state=success \ - -f context=Validate \ - -f description="All validation jobs completed successfully." + set -Eeuo pipefail + for result in \ + "${VALIDATE_SYNTAX_RESULT}" \ + "${TEST_INSTALL_MACOS_RESULT}" \ + "${TEST_INSTALL_LINUX_RESULT}"; do + [[ "${result}" == "success" ]] || { + printf 'Required validation dependency ended with: %s\n' "${result}" >&2 + exit 1 + } + done + echo "✅ All validation jobs completed successfully." diff --git a/ABOUT.md b/ABOUT.md new file mode 100644 index 0000000..bef4cc7 --- /dev/null +++ b/ABOUT.md @@ -0,0 +1,110 @@ +# About Homebrew Valhalla + +## Purpose + +Homebrew Valhalla packages reviewed snapshots and milestones from OpenJDK +Project Valhalla. It is intentionally narrower than a general OpenJDK tap: +only Valhalla-related releases belong in the canonical package family. + +## Release model + +Each supported feature line has a versioned formula: + +- `openjdk-valhalla@27` +- `openjdk-valhalla@28` + +`openjdk-valhalla` is an alias to the current line. Advancing that alias does +not remove older formulae, so users can keep projects on different Valhalla +lines and select them with jEnv. + +Every formula pins an immutable upstream commit archive, its SHA-256 checksum, +and a deterministic source timestamp. A bottle is built from that formula. +This keeps the binary and `--build-from-source` paths on the same source, +dependencies, configuration, vendor metadata, and formula revision. + +The tap does not invent a feature-line package when OpenJDK has not published a +corresponding Valhalla milestone or when a source pin cannot be verified. + +## Current provenance + +### JDK 27 milestone + +- Upstream binary milestone: `27-jep401ea3+1-1`, published 2026-03-11 +- Upstream source tag: `jep401ea3_branched` +- Source commit: `f9799f4c1a35694951413fda0986cdebe49f85d0` +- Commit timestamp: 2026-03-10T10:13:30Z +- Commit archive SHA-256: + `eb44694f4525aa7e57a6304d4c01f17ffaf78824ec76016e512742b643664367` + +### JDK 28 source line + +- Upstream branch: `lworld` +- Source commit: `f181286389fad995be1e71de60f30d14eb1c9122` +- Commit timestamp: 2026-07-27T11:53:10Z +- Commit archive SHA-256: + `d44923f1e68651f85080e53a27afd23fcc3ac23e0022bde7ba1309ca0d5bcf25` + +The JDK 27 source tag is the verified branch point for the published EA3 +milestone. The Artagon package identifies its exact source commit rather than +claiming byte identity with Oracle's convenience binary. + +## Repository migration and compatibility + +The intended canonical repository and tap are: + +```text +Artagon/homebrew-valhalla +Artagon/valhalla +``` + +The existing repository and tap are: + +```text +Artagon/homebrew-jdk26ea +Artagon/jdk26ea +``` + +Migration is split into two surfaces: + +1. The canonical Valhalla tap owns the versioned Valhalla formulae and bottle + releases. +2. The legacy tap keeps `Formula/jdk26ea.rb` and `Casks/jdk26ea.rb` available + with their existing tokens and install locations. + +This checkout contains both surfaces while the repository transition is being +prepared. Publishing or renaming GitHub repositories is a separate operation; +local refactoring does not perform that external change. + +Backward compatibility means: + +- Existing `brew tap Artagon/jdk26ea` commands remain valid. +- Existing `brew install jdk26ea` and `brew install --cask jdk26ea` commands + keep their current behavior. +- Existing installations are not moved or relinked. +- No legacy package is silently converted into a Valhalla package. + +## Build and bottle policy + +The source formulas require Apple Silicon, macOS Sonoma or newer, Xcode 15.4 +or newer with the optional Metal toolchain, Autoconf, GNU Make, pkgconf, +Homebrew native libraries, and OpenJDK 26 as the boot JDK. + +Source builds cap GNU Make at four jobs. Bottle jobs run only when manually +dispatched. They must build from the formula, run its smoke test, publish the +bottle, and merge Homebrew-generated checksums into the formula before the +bottle is advertised to users. + +Normal CI validates syntax, style, aliases, and legacy compatibility without +starting a multi-hour JDK build. + +## Support policy + +These packages are for experimentation and development. Valhalla early-access +features can change or disappear, builds can be incomplete, and security fixes +can lag supported OpenJDK releases. + +When reporting a problem, include the formula token and version, +`java --version`, macOS and Xcode versions, Apple Silicon model and available +memory, whether Homebrew poured a bottle or built from source, and relevant +Homebrew build logs. Do not include credentials, private paths, or unrelated +environment values. diff --git a/Aliases/openjdk-valhalla b/Aliases/openjdk-valhalla new file mode 120000 index 0000000..769ea4d --- /dev/null +++ b/Aliases/openjdk-valhalla @@ -0,0 +1 @@ +../Formula/openjdk-valhalla@28.rb \ No newline at end of file diff --git a/Casks/jdk26ea.rb b/Casks/jdk26ea.rb index 4e6f5ed..57a42e6 100644 --- a/Casks/jdk26ea.rb +++ b/Casks/jdk26ea.rb @@ -13,8 +13,6 @@ homepage "https://jdk.java.net/26/" postflight do - require "pathname" - staged_root = staged_path.realpath candidates = Dir["#{staged_root}/jdk-*.jdk"] odie "Expected exactly one JDK bundle in #{staged_root}, found #{candidates.length}" if candidates.length != 1 diff --git a/Formula/openjdk-valhalla@27.rb b/Formula/openjdk-valhalla@27.rb new file mode 100644 index 0000000..f7111b3 --- /dev/null +++ b/Formula/openjdk-valhalla@27.rb @@ -0,0 +1,127 @@ +class OpenjdkValhallaAT27 < Formula + desc "JDK 27 Valhalla JEP 401 early-access milestone built from source" + homepage "https://openjdk.org/projects/valhalla/" + url "https://github.com/openjdk/valhalla/archive/f9799f4c1a35694951413fda0986cdebe49f85d0.tar.gz" + version "27-ea-20260310-f9799f4c1a35" + sha256 "eb44694f4525aa7e57a6304d4c01f17ffaf78824ec76016e512742b643664367" + license "GPL-2.0-only" => { with: "Classpath-exception-2.0" } + + livecheck do + skip "pinned to the source tag for the published JEP 401 EA3 milestone" + end + + keg_only :versioned_formula + + depends_on "autoconf" => :build + depends_on "make" => :build + depends_on "pkgconf" => :build + depends_on xcode: ["15.4", :build] # for metal + depends_on arch: :arm64 + depends_on "freetype" + depends_on "giflib" + depends_on "harfbuzz" + depends_on "jpeg-turbo" + depends_on "libpng" + depends_on "little-cms2" + depends_on macos: :sonoma + + uses_from_macos "unzip" => :build + uses_from_macos "zip" => :build + uses_from_macos "cups" => :no_linkage + + resource "boot-jdk" do + url "https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/" \ + "openjdk-26_macos-aarch64_bin.tar.gz" + sha256 "254586bcd1bf6dcd125ad667ac32562cb1e2ab1abf3a61fb117b6fabb571e765" + end + + def install + boot_jdk = buildpath/"boot-jdk" + resource("boot-jdk").stage boot_jdk + boot_jdk /= "Contents/Home" + java_options = ENV.delete("_JAVA_OPTIONS") + + args = %W[ + --disable-warnings-as-errors + --with-boot-jdk-jvmargs=#{java_options} + --with-boot-jdk=#{boot_jdk} + --with-debug-level=release + --with-jvm-variants=server + --with-native-debug-symbols=none + --with-source-date=1773137610 + --with-vendor-bug-url=https://bugs.openjdk.org/ + --with-vendor-name=Artagon + --with-vendor-url=https://openjdk.org/projects/valhalla/ + --with-vendor-version-string=Artagon + --with-vendor-vm-bug-url=https://bugs.openjdk.org/ + --with-version-opt=valhalla-f9799f4c1a35 + --with-version-pre=ea + --without-version-build + --with-freetype=system + --with-giflib=system + --with-harfbuzz=system + --with-lcms=system + --with-libjpeg=system + --with-libpng=system + --with-zlib=system + ] + + ldflags = %W[ + -Wl,-rpath,#{loader_path.gsub("$", "\\$$")} + -Wl,-rpath,#{loader_path.gsub("$", "\\$$")}/server + -headerpad_max_install_names + ] + + inreplace "make/autoconf/lib-freetype.m4", '= "xmacosx"', '= ""' + + args += %W[ + --enable-dtrace + --with-extra-ldflags=#{ldflags.join(" ")} + --with-freetype-include=#{formula_opt_include("freetype")} + --with-freetype-lib=#{formula_opt_lib("freetype")} + --with-sysroot=#{MacOS.sdk_path} + ] + + system "bash", "configure", *args + ENV["MAKEFLAGS"] = "JOBS=#{[ENV.make_jobs, 4].min}" + system "gmake", "images" + + libexec.install Dir["build/*/images/jdk-bundle/*"].first => "openjdk.jdk" + jdk = libexec/"openjdk.jdk/Contents/Home" + bin.install_symlink Dir[jdk/"bin/*"] + include.install_symlink Dir[jdk/"include/*.h"] + include.install_symlink Dir[jdk/"include"/OS.kernel_name.downcase/"*.h"] + man1.install_symlink Dir[jdk/"man/man1/*"] + end + + def caveats + <<~EOS + For the system Java wrappers to find this JDK, symlink it with + sudo ln -sfn #{opt_libexec}/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-valhalla-27.jdk + EOS + end + + test do + (testpath/"ValueSmoke.java").write <<~JAVA + import java.util.Objects; + + class ValueSmoke { + value record Point(int x, int y) { + } + + public static void main(String[] args) { + Point first = new Point(3, 4); + Point second = new Point(3, 4); + System.out.println("equal = " + first.equals(second)); + System.out.println("identity = " + Objects.hasIdentity(first)); + } + } + JAVA + + system bin/"javac", "--enable-preview", "--release", "27", "ValueSmoke.java" + output = shell_output("#{bin}/java --enable-preview ValueSmoke") + assert_match "equal = true", output + assert_match "identity = false", output + assert_match "valhalla-f9799f4c1a35", shell_output("#{bin}/java --version 2>&1") + end +end diff --git a/Formula/openjdk-valhalla@28.rb b/Formula/openjdk-valhalla@28.rb new file mode 100644 index 0000000..9449db8 --- /dev/null +++ b/Formula/openjdk-valhalla@28.rb @@ -0,0 +1,128 @@ +class OpenjdkValhallaAT28 < Formula + desc "JDK 28 early-access OpenJDK build with Project Valhalla value objects" + homepage "https://openjdk.org/projects/valhalla/" + url "https://github.com/openjdk/valhalla/archive/f181286389fad995be1e71de60f30d14eb1c9122.tar.gz" + version "28-ea-20260727-f181286389fa" + sha256 "d44923f1e68651f85080e53a27afd23fcc3ac23e0022bde7ba1309ca0d5bcf25" + license "GPL-2.0-only" => { with: "Classpath-exception-2.0" } + version_scheme 1 + + livecheck do + skip "pinned to an adversarially reviewed Valhalla lworld commit" + end + + keg_only :versioned_formula + + depends_on "autoconf" => :build + depends_on "make" => :build + depends_on "pkgconf" => :build + depends_on xcode: ["15.4", :build] # for metal + depends_on arch: :arm64 + depends_on "freetype" + depends_on "giflib" + depends_on "harfbuzz" + depends_on "jpeg-turbo" + depends_on "libpng" + depends_on "little-cms2" + depends_on macos: :sonoma + + uses_from_macos "unzip" => :build + uses_from_macos "zip" => :build + uses_from_macos "cups" => :no_linkage + + resource "boot-jdk" do + url "https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/" \ + "openjdk-26_macos-aarch64_bin.tar.gz" + sha256 "254586bcd1bf6dcd125ad667ac32562cb1e2ab1abf3a61fb117b6fabb571e765" + end + + def install + boot_jdk = buildpath/"boot-jdk" + resource("boot-jdk").stage boot_jdk + boot_jdk /= "Contents/Home" + java_options = ENV.delete("_JAVA_OPTIONS") + + args = %W[ + --disable-warnings-as-errors + --with-boot-jdk-jvmargs=#{java_options} + --with-boot-jdk=#{boot_jdk} + --with-debug-level=release + --with-jvm-variants=server + --with-native-debug-symbols=none + --with-source-date=1785153190 + --with-vendor-bug-url=https://bugs.openjdk.org/ + --with-vendor-name=Artagon + --with-vendor-url=https://openjdk.org/projects/valhalla/ + --with-vendor-version-string=Artagon + --with-vendor-vm-bug-url=https://bugs.openjdk.org/ + --with-version-opt=valhalla-f181286389fa + --with-version-pre=ea + --without-version-build + --with-freetype=system + --with-giflib=system + --with-harfbuzz=system + --with-lcms=system + --with-libjpeg=system + --with-libpng=system + --with-zlib=system + ] + + ldflags = %W[ + -Wl,-rpath,#{loader_path.gsub("$", "\\$$")} + -Wl,-rpath,#{loader_path.gsub("$", "\\$$")}/server + -headerpad_max_install_names + ] + + inreplace "make/autoconf/lib-freetype.m4", '= "xmacosx"', '= ""' + + args += %W[ + --enable-dtrace + --with-extra-ldflags=#{ldflags.join(" ")} + --with-freetype-include=#{formula_opt_include("freetype")} + --with-freetype-lib=#{formula_opt_lib("freetype")} + --with-sysroot=#{MacOS.sdk_path} + ] + + system "bash", "configure", *args + ENV["MAKEFLAGS"] = "JOBS=#{[ENV.make_jobs, 4].min}" + system "gmake", "images" + + libexec.install Dir["build/*/images/jdk-bundle/*"].first => "openjdk.jdk" + jdk = libexec/"openjdk.jdk/Contents/Home" + bin.install_symlink Dir[jdk/"bin/*"] + include.install_symlink Dir[jdk/"include/*.h"] + include.install_symlink Dir[jdk/"include"/OS.kernel_name.downcase/"*.h"] + man1.install_symlink Dir[jdk/"man/man1/*"] + end + + def caveats + <<~EOS + For the system Java wrappers to find this JDK, symlink it with + sudo ln -sfn #{opt_libexec}/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-valhalla-28.jdk + EOS + end + + test do + (testpath/"ValueSmoke.java").write <<~JAVA + import java.util.Objects; + + class ValueSmoke { + value record Point(int x, int y) { + } + + public static void main(String[] args) { + Point first = new Point(3, 4); + Point second = new Point(3, 4); + System.out.println("equal = " + first.equals(second)); + System.out.println("identity = " + Objects.hasIdentity(first)); + } + } + JAVA + + system bin/"javac", "--enable-preview", "--release", "28", "ValueSmoke.java" + output = shell_output("#{bin}/java --enable-preview ValueSmoke") + assert_match "equal = true", output + assert_match "identity = false", output + assert_match "valhalla-f181286389fa", shell_output("#{bin}/java --version 2>&1") + end +end diff --git a/README.md b/README.md index e9beaef..b43e12d 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,142 @@ -# homebrew-jdk26 +# Homebrew Valhalla -Homebrew tap for JDK 26 Early Access builds with automated updates, CI/CD, and support for both macOS and Linux. +Homebrew tap for pinned OpenJDK Project Valhalla builds. It supports +side-by-side versioned JDKs, normal bottle installs, explicit source builds, +and the existing `jdk26ea` package names. -[![Release](https://github.com/Artagon/homebrew-jdk26ea/actions/workflows/release.yml/badge.svg)](https://github.com/Artagon/homebrew-jdk26ea/actions/workflows/release.yml) -[![Validate](https://github.com/Artagon/homebrew-jdk26ea/actions/workflows/validate.yml/badge.svg)](https://github.com/Artagon/homebrew-jdk26ea/actions/workflows/validate.yml) +This repository is the migration source for the canonical `Artagon/valhalla` +tap. Until that GitHub repository is published, use a local checkout for the +new tokens. The existing `Artagon/jdk26ea` tap and packages remain compatible. -## Quick Start +## Packages -### Cask Installation (macOS) +| Token | Source | Purpose | +| --- | --- | --- | +| `openjdk-valhalla` | Alias of `openjdk-valhalla@28` | Current rolling Valhalla line | +| `openjdk-valhalla@27` | OpenJDK tag `jep401ea3_branched` at `f9799f4c1a35` | Source line for the published JEP 401 EA3 milestone | +| `openjdk-valhalla@28` | Valhalla `lworld` commit `f181286389fa` | Reviewed JDK 28 EA source snapshot | +| `jdk26ea` | Existing OpenJDK 26 EA binaries | Backward compatibility; not a Valhalla release | -```bash -brew tap Artagon/jdk26ea -brew install --cask jdk26ea -``` +There is no `openjdk-valhalla@26` formula because no verified Project Valhalla +26 milestone is available from the current official release surface. The +legacy `jdk26ea` token is preserved, but it is not relabeled as Valhalla. -The cask installation places JDK in `/Library/Java/JavaVirtualMachines/jdk-26-ea.jdk` and integrates with macOS's Java management system. +The Valhalla formulas currently require Apple Silicon, macOS Sonoma or newer, +and Xcode 15.4 or newer. They are keg-only so multiple versions can coexist. -### Formula Installation (macOS/Linux) +## Install + +For a local checkout: ```bash -brew tap Artagon/jdk26ea -brew install jdk26ea +brew tap Artagon/valhalla /path/to/homebrew-valhalla ``` -The formula installation creates symlinks in your Homebrew bin directory. +After the canonical repository is published: -## Current Version +```bash +brew tap Artagon/valhalla +``` -**JDK 26 EA Build 20** (Released: 2025-10-17) +Install the current or a versioned line: -## About +```bash +brew install Artagon/valhalla/openjdk-valhalla +brew install Artagon/valhalla/openjdk-valhalla@27 +brew install Artagon/valhalla/openjdk-valhalla@28 +``` -This tap provides automated updates for JDK 26 Early Access builds from [jdk.java.net/26](https://jdk.java.net/26/). +Homebrew uses a compatible Artagon bottle when the formula contains a +published bottle block. If no compatible bottle exists, it builds from source. +Inspect `brew info` first when an unexpected long build would be a problem. -### Features +Force a source build from the same immutable source used for the bottle: -- Automatic updates when new EA builds are released -- Support for macOS (ARM64 & Intel) and Linux (ARM64 & x64) -- CI/CD validation with GitHub Actions -- Automatic GitHub releases on version updates -- Both cask and formula options +```bash +brew install --build-from-source Artagon/valhalla/openjdk-valhalla@28 +``` -## Usage +The source build is capped at four jobs to reduce memory-pressure failures. -### Setting JAVA_HOME +## jEnv -After installation, you may want to set `JAVA_HOME`: +Register each installed version by its JDK home: -**For cask installation:** ```bash -export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk-26-ea.jdk/Contents/Home" +jenv add "$(brew --prefix openjdk-valhalla@27)/libexec/openjdk.jdk/Contents/Home" +jenv add "$(brew --prefix openjdk-valhalla@28)/libexec/openjdk.jdk/Contents/Home" +jenv versions ``` -**For formula installation:** +Select the alias reported by `jenv add`: + ```bash -export JAVA_HOME="$(brew --prefix jdk26ea)" +jenv local ``` -### Verifying Installation +Use `jenv global ` only when Valhalla should be the default for every +jEnv-managed shell. + +## Backward compatibility + +Existing commands continue to use the legacy formula and cask: ```bash -java -version -# Should output: openjdk version "26-ea" ... +brew tap Artagon/jdk26ea +brew install jdk26ea +brew install --cask jdk26ea ``` -## Updating +Existing formula installs remain under the Homebrew prefix. Existing cask +installs remain at `/Library/Java/JavaVirtualMachines/jdk-26-ea.jdk`. -The tap is automatically updated with new EA builds. To update to the latest version: +The compatibility surface is frozen to the last published JDK 26 EA build. New +Valhalla users should use versioned `openjdk-valhalla@N` formulae. -```bash -brew update -brew upgrade jdk26ea # or brew upgrade --cask jdk26ea -``` +## Bottles -## Issue Reporting +The manually dispatched `Build Valhalla bottle` workflow: -Found a problem? [Open an issue](https://github.com/Artagon/homebrew-jdk26ea/issues/new/choose) using our issue templates. +1. Builds the selected formula from its pinned source on an Apple Silicon + macOS 14 runner with a read-only token and no persisted Git credential. +2. Runs the formula smoke test. +3. Validates the bottle name, formula, version, source revision, release URL, + and SHA-256 checksum. +4. Creates an immutable GitHub prerelease bound to the formula commit. +5. Opens a pull request with the generated bottle block. -## Automated Updates +Once that bottle-block pull request is merged, normal `brew install` selects +the bottle and `--build-from-source` selects the source build. Bottle +publication is deliberately manual because an OpenJDK build is expensive. -This repository uses GitHub Actions to: -- Validate cask/formula syntax on every commit -- Create GitHub releases when the version changes -- Run weekly audits to ensure quality +## Validation -## License +Fast local checks do not build a JDK: -This tap is distributed under the same license as OpenJDK (GPL-2.0 with Classpath Exception). +```bash +bats tests/formula_contract.bats +brew style Formula/*.rb Casks/*.rb +``` + +The existing `scripts/test.sh` also installs and tests the legacy `jdk26ea` +formula and cask. It is not the fast validation command. -## Disclaimer +The JDK 28 pin passed a direct image build and preview value-object smoke test. +Its full tier-one suite was interrupted. The JDK 27 source formula is pinned +and syntax-validated but has not completed a full source build in this +repository. -These are early-access builds provided for testing and development purposes. They are not intended for production use. For production environments, please use stable JDK releases. +For a jEnv-independent current shell: + +```bash +export JAVA_HOME="$(brew --prefix openjdk-valhalla@28)/libexec/openjdk.jdk/Contents/Home" +export PATH="$(brew --prefix openjdk-valhalla@28)/bin:${PATH}" +java --version +``` -## Links +See [ABOUT.md](ABOUT.md) for the release model, repository migration, support +policy, and source provenance. -- [JDK 26 EA Downloads](https://jdk.java.net/26/) -- [OpenJDK Project](https://openjdk.org/) -- [Homebrew Documentation](https://docs.brew.sh/) +Project Valhalla builds are early-access software. Features can change or +disappear, and these builds can lag security fixes available in supported JDK +releases. Do not treat them as production JDKs. diff --git a/SECURITY.md b/SECURITY.md index 2613fa6..5f52f3f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,142 +1,111 @@ # Security Policy -## Supported Versions - -This project provides automated updates for JDK 26 Early Access builds. As EA builds are not intended for production use, we only support the latest available build. - -| Version | Supported | -| ------- | ------------------ | -| Latest EA Build | :white_check_mark: | -| Older EA Builds | :x: | - -## Reporting a Vulnerability - -We take security seriously. If you discover a security vulnerability, please report it responsibly: - -### Where to Report - -- **GitHub Issues**: Open an issue at https://github.com/artagon/homebrew-jdk26ea/issues -- **For sensitive issues**: Please email the repository maintainer directly - -### What to Include - -1. Description of the vulnerability -2. Steps to reproduce -3. Potential impact -4. Suggested fix (if available) - -### Response Timeline - -- **Initial Response**: Within 48 hours -- **Status Update**: Within 7 days -- **Resolution**: Depends on severity and complexity - -## Supply Chain Security - -This project implements multiple security measures to ensure the integrity of JDK distributions: - -### 1. Manual Approval for Updates - -- All automated JDK updates require manual approval before distribution -- Updates create pull requests that must be reviewed and approved -- The `jdk-updates` environment enforces this requirement - -### 2. Checksum Verification - -- All JDK downloads are verified against official SHA256 checksums -- Checksums are fetched from official OpenJDK sources -- Downloads fail if checksums don't match - -### 3. URL Validation - -- Download URLs are validated against expected patterns -- Only downloads from `https://download.java.net/` are accepted -- URL structure is verified to match expected format - -### 4. Automated Security Scanning - -- **CodeQL**: Weekly security analysis of workflow code -- **Dependabot**: Automated dependency updates for Actions and Ruby gems -- **GitHub Actions**: All actions pinned to specific commit SHAs - -### 5. Build Verification - -- Ruby syntax validation for all formula/cask changes -- Homebrew audit checks before release -- Multi-platform installation testing (macOS ARM64/x64, Linux ARM64/x64) - -### 6. Timeout Protection - -- All network operations have timeouts (30s for metadata, 5min for downloads) -- Prevents hanging workflows and resource exhaustion -- Protects against slowloris-style attacks - -### 7. Branch Protection - -- Main branch requires passing status checks -- Pull request reviews required before merge -- Automated validation must pass - -## Security Best Practices - -### For Users - -1. **Verify Downloads**: Always verify the JDK installation after download - ```bash - java -version - ``` - -2. **Use Official Sources**: Only install from this tap or official OpenJDK sources - -3. **Stay Updated**: Regularly update to the latest EA build - ```bash - brew update - brew upgrade jdk26ea - ``` - -4. **EA Builds**: Remember that EA builds are for testing only, not production use - -### For Contributors - -1. **Pin Actions**: Always pin GitHub Actions to specific commit SHAs -2. **Quote Variables**: Quote shell variables in workflow scripts -3. **Add Timeouts**: Include timeouts for all network operations -4. **Test Locally**: Test formula/cask changes locally before submitting -5. **Follow Conventional Commits**: Use semantic commit messages - -## Known Limitations - -1. **EA Build Nature**: Early-access builds may contain bugs or security issues -2. **GPG Verification**: OpenJDK EA builds don't currently provide GPG signatures -3. **SLSA Provenance**: Not yet available for OpenJDK EA builds -4. **Multi-source Verification**: Currently relies on single upstream source - -## Planned Security Enhancements - -### Short-term (Next 3 months) - -- [ ] Implement GPG verification when available from OpenJDK -- [ ] Add SBOM (Software Bill of Materials) generation for releases -- [x] Set up workflow run approval for first-time contributors -- [ ] Document security policy for downstream consumers - -### Long-term (Next 6-12 months) - -- [ ] Implement SLSA provenance when OpenJDK supports it -- [ ] Multi-source verification (cross-check against multiple sources) -- [ ] Automated security policy compliance checks -- [ ] Integration with supply chain security tools (Sigstore, etc.) - -## Security Contacts - -- **Primary**: Repository issues - https://github.com/artagon/homebrew-jdk26ea/issues -- **Security Policy**: This document - https://github.com/artagon/homebrew-jdk26ea/blob/main/SECURITY.md - -## Acknowledgments - -We thank the security community for responsible disclosure practices and the OpenJDK project for providing transparent build processes. - ---- - -**Last Updated**: 2025-10-25 -**Policy Version**: 1.0.0 +## Supported packages + +| Package | Security support | +| --- | --- | +| `openjdk-valhalla@27` | Best-effort packaging and workflow fixes | +| `openjdk-valhalla@28` and `openjdk-valhalla` | Best-effort packaging and workflow fixes | +| `jdk26ea` formula and cask | Frozen compatibility surface | + +Valhalla and JDK early-access builds are experimental. They can lag security +fixes available in supported OpenJDK releases and must not be treated as +production JDKs. + +## Reporting a vulnerability + +Do not publish exploit details, credentials, private paths, or sensitive logs +in a public issue. Use GitHub's private vulnerability-reporting or security +advisory flow when it is available for this repository. Otherwise contact the +repository owner privately and include: + +1. Affected formula, workflow, release, or commit. +2. Reproduction steps. +3. Expected impact. +4. A minimal remediation, if known. + +Public, non-sensitive defects can be reported through the repository issue +tracker. + +## Enforced repository controls + +The repository is configured with the following controls: + +- The default `GITHUB_TOKEN` permission is read-only and cannot approve pull + request reviews. +- GitHub Actions references must use immutable full commit SHAs. +- Pull-request validation has only `contents: read`; checkout credentials are + not persisted. +- The required `Validation Status` check is bound to the GitHub Actions app and + must run against an up-to-date branch. +- `main` requires one approving review, dismissal of stale reviews, approval + after the latest push, conversation resolution, and administrator + enforcement. +- Force pushes and branch deletion are disabled. +- Release immutability prevents a published tag or asset from being replaced. + +This repository currently has one collaborator. A second trusted collaborator +must be added before a protected pull request can receive the required +non-author approval. + +## Bottle supply-chain controls + +The bottle workflow: + +1. Can be dispatched only from `main` for one of the enumerated versioned + formulae. +2. Builds external OpenJDK source with only `contents: read` and without a + persisted Git credential. +3. Uses a release tag containing both the formula version and formula commit. +4. Generates artifacts in a dedicated directory. +5. Requires exactly one bottle archive and one Homebrew bottle JSON document. +6. Validates the formula name, version, tap path, source revision, release root + URL, platform tag, local and remote filenames, and SHA-256 checksum. +7. Revalidates the downloaded workflow artifact before granting a release step + access to a write token. +8. Refuses to replace an existing release or asset. +9. Opens a pull request for the Homebrew-generated bottle block. + +Normal pull-request validation does not build OpenJDK. + +## Source and dependency controls + +- Formula source archives and boot JDK resources use immutable URLs and + SHA-256 checksums. +- Legacy binary updates validate the expected download host, checksum format, + and downloaded file before opening a pull request. +- Formula and cask changes run Ruby syntax checks, Homebrew style/audit checks, + and compatibility tests. +- Workflow YAML runs actionlint, ShellCheck, and executable security-contract + tests locally before publication. + +## Known limitations + +- OpenJDK EA artifacts do not currently provide a project-specific signing + chain consumed by these formulae. +- Source archive and checksum availability ultimately depend on upstream + GitHub and OpenJDK infrastructure. +- A checksum fetched from the same upstream origin as a legacy binary is not + independent provenance. +- A complete JDK 28 tier-one test run has not been recorded for the current + pin. The earlier run was interrupted. +- No SLSA provenance claim is made for Artagon bottles. + +## User guidance + +Inspect the selected installation path before starting: + +```bash +brew info Artagon/valhalla/openjdk-valhalla@28 +``` + +After installation: + +```bash +java --version +``` + +Use `--build-from-source` only when a long local build is intended. A normal +install uses a compatible published bottle when one is available. + +**Last updated:** 2026-07-28 diff --git a/scripts/test.sh b/scripts/test.sh index 7d41e51..2349833 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -20,6 +20,7 @@ abort() { } command -v brew >/dev/null 2>&1 || abort "Homebrew is required to run these tests." +command -v bats >/dev/null 2>&1 || abort "bats-core is required to run these tests." cleanup_cmds=() register_cleanup() { @@ -58,9 +59,12 @@ link_tap_to_repo() { } run_style_and_audit() { + log "Running compatibility contract tests" + bats "${ROOT}/tests/formula_contract.bats" + log "Running brew style checks" brew style "${ROOT}/Casks/${FORMULA_NAME}.rb" - brew style "${ROOT}/Formula/${FORMULA_NAME}.rb" + brew style "${ROOT}"/Formula/*.rb log "Running brew audit for formula ${FORMULA_FULL}" brew audit --formula "${FORMULA_FULL}" diff --git a/scripts/update.sh b/scripts/update.sh index 0a3ca39..3b22244 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -73,8 +73,9 @@ linux_x64_url="${base_url}/openjdk-${version}_linux-x64_bin.tar.gz" get_remote_sha256() { local url="$1" local sha_url="${url}.sha256" + local sha - local sha=$(curl -fsSL "$sha_url" 2>/dev/null | awk '{print $1}') + sha=$(curl -fsSL "$sha_url" 2>/dev/null | awk '{print $1}') if [[ -z "$sha" || ! "$sha" =~ ^[a-f0-9]{64}$ ]]; then log_error "Failed to get valid SHA256 for $url" diff --git a/scripts/validate-bottle-artifact.rb b/scripts/validate-bottle-artifact.rb new file mode 100644 index 0000000..f3c41e2 --- /dev/null +++ b/scripts/validate-bottle-artifact.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby + +require "digest" +require "json" +require "optparse" +require "pathname" +require "uri" + +options = {} +OptionParser.new do |parser| + parser.on("--directory PATH") { |value| options[:directory] = value } + parser.on("--formula TOKEN") { |value| options[:formula] = value } + parser.on("--tap NAME") { |value| options[:tap] = value } + parser.on("--version VERSION") { |value| options[:version] = value } + parser.on("--git-revision SHA") { |value| options[:git_revision] = value } + parser.on("--root-url URL") { |value| options[:root_url] = value } + parser.on("--github-output PATH") { |value| options[:github_output] = value } +end.parse! + +def fail_validation(message) + warn "ERROR: #{message}" + exit 1 +end + +required = %i[directory formula tap version git_revision root_url github_output] +missing = required.reject { |key| options[key] && !options[key].empty? } +fail_validation("missing required options: #{missing.join(", ")}") unless missing.empty? + +formula = options.fetch(:formula) +tap = options.fetch(:tap) +version = options.fetch(:version) +git_revision = options.fetch(:git_revision) +root_url = options.fetch(:root_url) + +fail_validation("invalid formula token") unless formula.match?(/\Aopenjdk-valhalla@\d+\z/) +fail_validation("invalid tap name") unless tap.match?(%r{\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\z}) +fail_validation("invalid formula version") unless version.match?(/\A[0-9A-Za-z][0-9A-Za-z._+-]*\z/) +fail_validation("invalid git revision") unless git_revision.match?(/\A[0-9a-f]{40}\z/) + +uri = URI.parse(root_url) +unless uri.is_a?(URI::HTTPS) && uri.host == "github.com" && uri.userinfo.nil? && + uri.query.nil? && uri.fragment.nil? && uri.path.start_with?("/") + fail_validation("invalid bottle root URL") +end + +directory = Pathname(options.fetch(:directory)).realpath +fail_validation("artifact directory is not a directory") unless directory.directory? + +bottle_paths = directory.children.select { |path| path.file? && path.basename.to_s.end_with?(".bottle.tar.gz") } +json_paths = directory.children.select { |path| path.file? && path.basename.to_s.end_with?(".bottle.json") } +fail_validation("expected exactly one bottle archive, found #{bottle_paths.length}") unless bottle_paths.one? +fail_validation("expected exactly one bottle JSON, found #{json_paths.length}") unless json_paths.one? + +bottle_path = bottle_paths.first +json_path = json_paths.first +fail_validation("bottle archive must not be a symlink") if bottle_path.symlink? +fail_validation("bottle JSON must not be a symlink") if json_path.symlink? + +begin + document = JSON.parse(json_path.read) +rescue JSON::ParserError => e + fail_validation("invalid bottle JSON: #{e.message}") +end + +full_name = "#{tap}/#{formula}" +fail_validation("bottle JSON must contain only #{full_name}") unless document.keys == [full_name] + +entry = document.fetch(full_name) +formula_metadata = entry.fetch("formula") { fail_validation("formula metadata is missing") } +bottle_metadata = entry.fetch("bottle") { fail_validation("bottle metadata is missing") } + +fail_validation("formula name does not match") unless formula_metadata["name"] == formula +fail_validation("formula version does not match") unless formula_metadata["pkg_version"] == version +unless formula_metadata["tap_git_path"] == "Formula/#{formula}.rb" + fail_validation("formula path does not match") +end +unless formula_metadata["tap_git_revision"] == git_revision + fail_validation("git revision does not match") +end +fail_validation("bottle root URL does not match") unless bottle_metadata["root_url"] == root_url + +tags = bottle_metadata["tags"] +fail_validation("expected exactly one bottle tag") unless tags.is_a?(Hash) && tags.one? +tag, tag_metadata = tags.first +fail_validation("unexpected bottle tag #{tag}") unless tag.match?(/\Aarm64_[a-z0-9_]+\z/) + +local_filename = tag_metadata["local_filename"] +remote_filename = tag_metadata["filename"] +fail_validation("local bottle filename does not match") unless local_filename == bottle_path.basename.to_s +fail_validation("unsafe remote bottle filename") unless remote_filename.is_a?(String) && + File.basename(remote_filename) == remote_filename +decoded_remote_filename = URI::DEFAULT_PARSER.unescape(remote_filename) +unless decoded_remote_filename == local_filename + fail_validation("remote bottle filename does not match") +end + +expected_sha256 = tag_metadata["sha256"] +fail_validation("invalid bottle SHA-256") unless expected_sha256&.match?(/\A[0-9a-f]{64}\z/) +actual_sha256 = Digest::SHA256.file(bottle_path).hexdigest +fail_validation("bottle SHA-256 does not match") unless actual_sha256 == expected_sha256 + +output_values = { + "bottle_path" => bottle_path.realpath.to_s, + "json_path" => json_path.realpath.to_s, +} +if output_values.values.any? { |value| value.match?(/[\r\n]/) } + fail_validation("artifact path contains a newline") +end + +File.open(options.fetch(:github_output), "a", 0o600) do |output| + output_values.each { |key, value| output.puts "#{key}=#{value}" } +end + +puts "Validated #{formula} #{version} bottle #{bottle_path.basename}" diff --git a/tests/bottle_artifact_validator_test.rb b/tests/bottle_artifact_validator_test.rb new file mode 100644 index 0000000..d39926c --- /dev/null +++ b/tests/bottle_artifact_validator_test.rb @@ -0,0 +1,113 @@ +require "digest" +require "fileutils" +require "json" +require "minitest/autorun" +require "open3" +require "tmpdir" + +class BottleArtifactValidatorTest < Minitest::Test + ROOT = File.expand_path("..", __dir__) + SCRIPT = File.join(ROOT, "scripts", "validate-bottle-artifact.rb") + FORMULA = "openjdk-valhalla@28" + TAP = "artagon/valhalla" + VERSION = "28-ea-20260727-f181286389fa" + REVISION = "0123456789abcdef0123456789abcdef01234567" + ROOT_URL = "https://github.com/artagon/homebrew-jdk26ea/releases/download/bottle-openjdk-valhalla-28-test" + BOTTLE = "openjdk-valhalla@28--28-ea-20260727-f181286389fa.arm64_sonoma.bottle.tar.gz" + + def run_validator(directory, output) + Open3.capture3( + RbConfig.ruby, + SCRIPT, + "--directory", directory, + "--formula", FORMULA, + "--tap", TAP, + "--version", VERSION, + "--git-revision", REVISION, + "--root-url", ROOT_URL, + "--github-output", output, + ) + end + + def write_valid_artifact(directory) + bottle_path = File.join(directory, BOTTLE) + File.binwrite(bottle_path, "immutable bottle payload") + sha256 = Digest::SHA256.file(bottle_path).hexdigest + json_path = File.join(directory, "#{FORMULA}--#{VERSION}.bottle.json") + payload = { + "#{TAP}/#{FORMULA}" => { + "formula" => { + "name" => FORMULA, + "pkg_version" => VERSION, + "tap_git_path" => "Formula/#{FORMULA}.rb", + "tap_git_revision" => REVISION, + }, + "bottle" => { + "root_url" => ROOT_URL, + "tags" => { + "arm64_sonoma" => { + "filename" => BOTTLE, + "local_filename" => BOTTLE, + "sha256" => sha256, + }, + }, + }, + }, + } + File.write(json_path, JSON.pretty_generate(payload)) + [bottle_path, json_path] + end + + def test_accepts_one_matching_bottle_and_writes_exact_paths + Dir.mktmpdir do |directory| + bottle_path, json_path = write_valid_artifact(directory) + output = File.join(directory, "github-output") + + _stdout, stderr, status = run_validator(directory, output) + + assert status.success?, stderr + assert_equal( + "bottle_path=#{File.realpath(bottle_path)}\njson_path=#{File.realpath(json_path)}\n", + File.read(output), + ) + end + end + + def test_rejects_additional_bottle_files + Dir.mktmpdir do |directory| + write_valid_artifact(directory) + File.binwrite(File.join(directory, "unexpected.bottle.tar.gz"), "extra") + + _stdout, stderr, status = run_validator(directory, File.join(directory, "output")) + + refute status.success? + assert_includes stderr, "expected exactly one bottle archive" + end + end + + def test_rejects_metadata_from_another_git_revision + Dir.mktmpdir do |directory| + _bottle_path, json_path = write_valid_artifact(directory) + payload = JSON.parse(File.read(json_path)) + payload.fetch("#{TAP}/#{FORMULA}").fetch("formula")["tap_git_revision"] = "f" * 40 + File.write(json_path, JSON.pretty_generate(payload)) + + _stdout, stderr, status = run_validator(directory, File.join(directory, "output")) + + refute status.success? + assert_includes stderr, "git revision does not match" + end + end + + def test_rejects_checksum_mismatch + Dir.mktmpdir do |directory| + bottle_path, _json_path = write_valid_artifact(directory) + File.binwrite(bottle_path, "modified after metadata generation") + + _stdout, stderr, status = run_validator(directory, File.join(directory, "output")) + + refute status.success? + assert_includes stderr, "SHA-256 does not match" + end + end +end diff --git a/tests/formula_contract.bats b/tests/formula_contract.bats new file mode 100644 index 0000000..04e2227 --- /dev/null +++ b/tests/formula_contract.bats @@ -0,0 +1,35 @@ +#!/usr/bin/env bats + +setup() { + REPO_ROOT="$(cd -- "${BATS_TEST_DIRNAME}/.." && pwd -P)" +} + +@test "supported Valhalla source formulae are valid Ruby" { + local formula + + for formula in \ + "${REPO_ROOT}/Formula/openjdk-valhalla@27.rb" \ + "${REPO_ROOT}/Formula/openjdk-valhalla@28.rb"; do + run ruby -c "${formula}" + + [ "${status}" -eq 0 ] + [ "${output}" = "Syntax OK" ] + done +} + +@test "rolling Valhalla token resolves to the current source line" { + run readlink "${REPO_ROOT}/Aliases/openjdk-valhalla" + + [ "${status}" -eq 0 ] + [ "${output}" = "../Formula/openjdk-valhalla@28.rb" ] +} + +@test "legacy jdk26ea formula and cask remain valid Ruby" { + run ruby -c "${REPO_ROOT}/Formula/jdk26ea.rb" + [ "${status}" -eq 0 ] + [ "${output}" = "Syntax OK" ] + + run ruby -c "${REPO_ROOT}/Casks/jdk26ea.rb" + [ "${status}" -eq 0 ] + [ "${output}" = "Syntax OK" ] +} diff --git a/tests/workflow_security_test.rb b/tests/workflow_security_test.rb new file mode 100644 index 0000000..8aea2e1 --- /dev/null +++ b/tests/workflow_security_test.rb @@ -0,0 +1,136 @@ +require "minitest/autorun" +require "yaml" + +class WorkflowSecurityTest < Minitest::Test + ROOT = File.expand_path("..", __dir__) + + def workflow(name) + YAML.load_file(File.join(ROOT, ".github", "workflows", name)) + end + + def checkout_steps(document) + document.fetch("jobs").values.flat_map { |job| job.fetch("steps", []) } + .select { |step| step["uses"]&.start_with?("actions/checkout@") } + end + + def test_bottle_build_cannot_write_repository_state + document = workflow("bottles.yml") + build = document.fetch("jobs").fetch("build") + + assert_equal({ "contents" => "read" }, build["permissions"]) + assert checkout_steps({ "jobs" => { "build" => build } }).all? { + |step| step.dig("with", "persist-credentials") == false + } + end + + def test_bottle_publish_scopes_write_token_to_release_step + document = workflow("bottles.yml") + publish = document.fetch("jobs").fetch("publish") + + assert_equal( + { "contents" => "write", "pull-requests" => "write" }, + publish["permissions"], + ) + refute publish.fetch("env", {}).key?("GH_TOKEN") + + release_step = publish.fetch("steps").find { |step| step["name"] == "Publish bottle release" } + assert_equal "${{ github.token }}", release_step.fetch("env").fetch("GH_TOKEN") + end + + def test_pull_request_validation_is_read_only_without_persisted_credentials + document = workflow("validate.yml") + + assert_equal({ "contents" => "read" }, document["permissions"]) + assert checkout_steps(document).all? { + |step| step.dig("with", "persist-credentials") == false + } + + status_job = document.fetch("jobs").fetch("validation-status") + assert_equal({ "contents" => "read" }, status_job["permissions"]) + refute status_job.fetch("steps").any? { |step| step.fetch("env", {}).key?("GH_TOKEN") } + end + + def test_required_validation_status_fails_when_any_dependency_does_not_succeed + status_job = workflow("validate.yml").fetch("jobs").fetch("validation-status") + confirm_step = status_job.fetch("steps").find { |step| step["name"] == "Confirm completion" } + + assert_equal "${{ always() }}", status_job["if"] + assert_equal( + { + "VALIDATE_SYNTAX_RESULT" => "${{ needs.validate-syntax.result }}", + "TEST_INSTALL_MACOS_RESULT" => "${{ needs.test-install-macos.result }}", + "TEST_INSTALL_LINUX_RESULT" => "${{ needs.test-install-linux.result }}", + }, + confirm_step["env"], + ) + assert_includes confirm_step.fetch("run"), '[[ "${result}" == "success" ]]' + assert_includes confirm_step.fetch("run"), "exit 1" + end + + def test_validation_gates_workflow_and_shell_lint + steps = workflow("validate.yml").fetch("jobs").fetch("validate-syntax").fetch("steps") + install = steps.find { |step| step["name"] == "Install validation tools" } + lint = steps.find { |step| step["name"] == "Lint workflows and shell scripts" } + + assert_includes install.fetch("run"), "actionlint" + assert_includes install.fetch("run"), "shellcheck" + assert_includes lint.fetch("run"), "actionlint .github/workflows/*.yml" + assert_includes lint.fetch("run"), "shellcheck scripts/test.sh scripts/update.sh tests/formula_contract.bats" + end + + def test_every_action_reference_is_an_immutable_commit + action_references = Dir[File.join(ROOT, ".github", "workflows", "*.yml")].flat_map do |path| + workflow = YAML.load_file(path) + workflow.fetch("jobs").values.flat_map { |job| job.fetch("steps", []) } + .map { |step| step["uses"] } + .compact + end + + action_references.each do |reference| + assert_match(%r{\A[^@\s]+@[0-9a-f]{40}\z}, reference) + end + end + + def test_bottle_release_is_bound_to_main_commit_and_is_immutable + document = workflow("bottles.yml") + build = document.fetch("jobs").fetch("build") + publish = document.fetch("jobs").fetch("publish") + metadata = build.fetch("steps").find { |step| step["name"] == "Resolve bottle metadata" } + release_step = publish.fetch("steps").find { |step| step["name"] == "Publish bottle release" } + + assert_includes metadata.fetch("run"), '[[ "${GITHUB_REF}" == "refs/heads/main" ]]' + assert_includes metadata.fetch("run"), "${GITHUB_SHA::12}" + assert_includes release_step.fetch("run"), '--target "${GITHUB_SHA}"' + assert_includes release_step.fetch("run"), '"repos/${GITHUB_REPOSITORY}/git/refs"' + assert_includes release_step.fetch("run"), 'ref="refs/tags/${BOTTLE_TAG}"' + assert_includes release_step.fetch("run"), 'sha="${GITHUB_SHA}"' + assert_includes release_step.fetch("run"), "--verify-tag" + assert_operator( + release_step.fetch("run").index('"repos/${GITHUB_REPOSITORY}/git/refs"'), + :<, + release_step.fetch("run").index("gh release create"), + ) + refute_includes release_step.fetch("run"), "--clobber" + end + + def test_bottle_artifact_paths_come_from_validator_outputs + document = workflow("bottles.yml") + build_steps = document.fetch("jobs").fetch("build").fetch("steps") + publish_steps = document.fetch("jobs").fetch("publish").fetch("steps") + upload = build_steps.find { |step| step["name"] == "Upload bottle job artifact" } + validate = publish_steps.find { |step| step["name"] == "Validate bottle artifact" } + release_step = publish_steps.find { |step| step["name"] == "Publish bottle release" } + merge = publish_steps.find { |step| step["name"] == "Merge bottle metadata into formula" } + + assert_equal( + "${{ steps.artifact.outputs.bottle_path }}\n${{ steps.artifact.outputs.json_path }}", + upload.fetch("with").fetch("path"), + ) + assert validate, "missing Validate bottle artifact step" + assert_includes validate.fetch("run"), "scripts/validate-bottle-artifact.rb" + assert_includes release_step.fetch("run"), '"${BOTTLE_PATH}"' + assert_includes merge.fetch("run"), '"${BOTTLE_JSON}"' + refute_includes release_step.fetch("run"), "*.tar.gz" + refute_includes merge.fetch("run"), "find bottle-artifact" + end +end From c85a37171d8994840b8c77cf4af1a604f88c45f2 Mon Sep 17 00:00:00 2001 From: trumpyla Date: Tue, 28 Jul 2026 09:03:51 -0400 Subject: [PATCH 2/3] fix(workflow): pin first-interaction to valid commit --- .github/workflows/first-time-contributor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/first-time-contributor.yml b/.github/workflows/first-time-contributor.yml index 3c247bd..c421499 100644 --- a/.github/workflows/first-time-contributor.yml +++ b/.github/workflows/first-time-contributor.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if first-time contributor - uses: actions/first-interaction@34f15e814fe6a2a25f9e1a0ca5cd8249ac4c4c74 # v1.3.0 + uses: actions/first-interaction@1c4688942c71f71d4f5502a26ea67c331730fa4d # v3.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: | From bc34b97a7daa54f29e0f06b3a0d44fb521ead1d3 Mon Sep 17 00:00:00 2001 From: trumpyla Date: Tue, 28 Jul 2026 09:10:08 -0400 Subject: [PATCH 3/3] fix: restore legacy JDK 26 compatibility --- .github/workflows/auto-update.yml | 207 ---------------------------- .github/workflows/release.yml | 212 ---------------------------- .github/workflows/update.yml | 201 --------------------------- .github/workflows/validate.yml | 2 +- ABOUT.md | 4 +- Casks/jdk26ea.rb | 14 +- Formula/jdk26ea.rb | 22 +-- README.md | 7 +- SECURITY.md | 4 +- scripts/test.sh | 2 +- scripts/update.sh | 220 ------------------------------ tests/formula_contract.bats | 17 +++ tests/workflow_security_test.rb | 2 +- 13 files changed, 46 insertions(+), 868 deletions(-) delete mode 100644 .github/workflows/auto-update.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/update.yml delete mode 100755 scripts/update.sh diff --git a/.github/workflows/auto-update.yml b/.github/workflows/auto-update.yml deleted file mode 100644 index 0b00957..0000000 --- a/.github/workflows/auto-update.yml +++ /dev/null @@ -1,207 +0,0 @@ -name: Legacy JDK 26 EA update - -on: - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - check-update: - runs-on: ubuntu-latest - environment: - name: jdk-updates - url: https://github.com/${{ github.repository }}/pull/${{ steps.pr.outputs.pull-request-number }} - steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Cache JDK downloads - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: /tmp/jdk-downloads - key: ${{ runner.os }}-jdk26ea-downloads-${{ github.run_id }} - restore-keys: | - ${{ runner.os }}-jdk26ea-downloads- - - - name: Fetch latest JDK 26 EA version - id: fetch_version - run: | - set -euo pipefail - # Fetch the JDK 26 EA page and extract the latest build number - page=$(curl --max-time 30 -fsSL "https://jdk.java.net/26/") - - # Extract build number from the page - build=$(echo "$page" | grep -oP 'Build \K\d+' | head -1) - - if [[ -z "$build" || ! "$build" =~ ^[0-9]+$ ]]; then - echo "Failed to fetch build number" - exit 1 - fi - - echo "Latest build: $build" - echo "build=$build" >>"$GITHUB_OUTPUT" - - # Get current version from cask - current_version=$(grep -oP 'version "\K[^"]+' Casks/jdk26ea.rb) - current_build=$(echo "$current_version" | grep -oP '\+\K\d+') - - if [[ -z "$current_build" || ! "$current_build" =~ ^[0-9]+$ ]]; then - echo "Failed to determine current build from Casks/jdk26ea.rb" - exit 1 - fi - - echo "Current build: $current_build" - echo "current_build=$current_build" >>"$GITHUB_OUTPUT" - - if [ "$build" != "$current_build" ]; then - echo "needs_update=true" >>"$GITHUB_OUTPUT" - echo "New version available!" - else - echo "needs_update=false" >>"$GITHUB_OUTPUT" - echo "Already up to date" - fi - - - name: Download and compute checksums - if: steps.fetch_version.outputs.needs_update == 'true' - id: checksums - run: | - set -euo pipefail - build="${{ steps.fetch_version.outputs.build }}" - - # Create cache directory - mkdir -p /tmp/jdk-downloads - - # Download URLs - declare -A urls=( - [mac_arm]="https://download.java.net/java/early_access/jdk26/${build}/GPL/openjdk-26-ea+${build}_macos-aarch64_bin.tar.gz" - [mac_x64]="https://download.java.net/java/early_access/jdk26/${build}/GPL/openjdk-26-ea+${build}_macos-x64_bin.tar.gz" - [linux_arm]="https://download.java.net/java/early_access/jdk26/${build}/GPL/openjdk-26-ea+${build}_linux-aarch64_bin.tar.gz" - [linux_x64]="https://download.java.net/java/early_access/jdk26/${build}/GPL/openjdk-26-ea+${build}_linux-x64_bin.tar.gz" - ) - - for key in mac_arm mac_x64 linux_arm linux_x64; do - url=${urls[$key]} - sha=$(curl --max-time 30 -fsSL "${url}.sha256" | awk '{print $1}') - if [[ -z "$sha" || ! "$sha" =~ ^[0-9a-f]{64}$ ]]; then - echo "Invalid checksum for ${key}: $sha" - exit 1 - fi - - # Use cached download if available - cache_file="/tmp/jdk-downloads/$(basename "$url")" - if [[ -f "$cache_file" ]]; then - echo "Using cached file for ${key}" - actual=$(shasum -a 256 "$cache_file" | awk '{print $1}') - else - echo "Downloading ${key}..." - curl --max-time 300 -fsSL "$url" -o "$cache_file" - actual=$(shasum -a 256 "$cache_file" | awk '{print $1}') - fi - - if [[ "$actual" != "$sha" ]]; then - echo "Checksum mismatch for ${key}. Deleting cached file and failing." - rm -f "$cache_file" - exit 1 - fi - printf 'sha_%s=%s\n' "$key" "$sha" >> "$GITHUB_OUTPUT" - done - - echo "Checksums validated successfully" - - - name: Update cask - if: steps.fetch_version.outputs.needs_update == 'true' - run: | - set -Eeuo pipefail - build="${{ steps.fetch_version.outputs.build }}" - - # Update Cask - new_cask_version="26-ea+${build},${build}" - sed -i "s|version \".*\"|version \"${new_cask_version}\"|g" Casks/jdk26ea.rb - - # Update checksums in Cask - awk -v arm="${{ steps.checksums.outputs.sha_mac_arm }}" \ - -v intel="${{ steps.checksums.outputs.sha_mac_x64 }}" \ - '/sha256 arm:/ { - print " sha256 arm: \"" arm "\"," - print " intel: \"" intel "\"" - next - } - {print}' Casks/jdk26ea.rb > Casks/jdk26ea.rb.tmp && mv Casks/jdk26ea.rb.tmp Casks/jdk26ea.rb - - - name: Update formula - if: steps.fetch_version.outputs.needs_update == 'true' - run: | - set -Eeuo pipefail - build="${{ steps.fetch_version.outputs.build }}" - current_build="${{ steps.fetch_version.outputs.current_build }}" - - # Update Formula - sed -i "s/26-ea+${current_build}/26-ea+${build}/g" Formula/jdk26ea.rb - sed -i "s|jdk26/${current_build}/GPL|jdk26/${build}/GPL|g" Formula/jdk26ea.rb - - # Update checksums in Formula (supports both macOS and Linux ARM64/x64) - awk -v mac_arm="${{ steps.checksums.outputs.sha_mac_arm }}" \ - -v mac_x64="${{ steps.checksums.outputs.sha_mac_x64 }}" \ - -v linux_arm="${{ steps.checksums.outputs.sha_linux_arm }}" \ - -v linux_x64="${{ steps.checksums.outputs.sha_linux_x64 }}" \ - 'BEGIN { in_macos=0; in_linux=0; in_arm_block=0 } - /on_macos do/ { in_macos=1; in_linux=0 } - /on_linux do/ { in_linux=1; in_macos=0 } - /if Hardware::CPU\.arm\?/ { in_arm_block=1 } - /else$/ { in_arm_block=0 } - /^ end$/ { - if (in_macos || in_linux) { - in_macos=0 - in_linux=0 - } - } - /sha256/ { - if (in_macos && in_arm_block) { sub(/sha256 ".*"/, "sha256 \"" mac_arm "\"") } - else if (in_macos && !in_arm_block) { sub(/sha256 ".*"/, "sha256 \"" mac_x64 "\"") } - else if (in_linux && in_arm_block) { sub(/sha256 ".*"/, "sha256 \"" linux_arm "\"") } - else if (in_linux && !in_arm_block) { sub(/sha256 ".*"/, "sha256 \"" linux_x64 "\"") } - } - {print}' Formula/jdk26ea.rb > Formula/jdk26ea.rb.tmp && mv Formula/jdk26ea.rb.tmp Formula/jdk26ea.rb - - - name: Validate syntax - if: steps.fetch_version.outputs.needs_update == 'true' - run: | - ruby -c Casks/jdk26ea.rb - ruby -c Formula/jdk26ea.rb - - - name: Create Pull Request - if: steps.fetch_version.outputs.needs_update == 'true' - id: pr - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore: update to JDK 26 EA Build ${{ steps.fetch_version.outputs.build }}" - title: "chore: update to JDK 26 EA Build ${{ steps.fetch_version.outputs.build }}" - body: | - ## Automated Update - - This PR updates the JDK 26 EA cask and formula to Build ${{ steps.fetch_version.outputs.build }}. - - ### Changes - - Updated version from `26-ea+${{ steps.fetch_version.outputs.current_build }}` to `26-ea+${{ steps.fetch_version.outputs.build }}` - - Updated download URLs - - Updated SHA256 checksums for all platforms - - ### Checksums - - macOS ARM64: `${{ steps.checksums.outputs.sha_mac_arm }}` - - macOS x64: `${{ steps.checksums.outputs.sha_mac_x64 }}` - - Linux ARM64: `${{ steps.checksums.outputs.sha_linux_arm }}` - - Linux x64: `${{ steps.checksums.outputs.sha_linux_x64 }}` - - ### Source - https://jdk.java.net/26/ - - --- - This PR was automatically created by the auto-update workflow. - branch: update/jdk26-ea-build-${{ steps.fetch_version.outputs.build }} - delete-branch: true - labels: | - automated - update diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index d1b2322..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,212 +0,0 @@ -name: Release - -on: - push: - branches: [main] - paths: - - 'Casks/jdk26ea.rb' - - 'Formula/jdk26ea.rb' - -permissions: - contents: write - pull-requests: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - fetch-depth: 0 # Fetch all history for changelog - persist-credentials: false - - - name: Set up Homebrew - if: runner.os == 'Linux' - uses: Homebrew/actions/setup-homebrew@c39f0335940fb3214046dce5a5d2f94ed275ab4b - - - name: Audit cask - run: | - brew untap artagon/jdk26ea || true - brew tap --custom-remote artagon/jdk26ea "$GITHUB_WORKSPACE" - brew audit --cask artagon/jdk26ea/jdk26ea - - - name: Extract version from cask - id: version - run: | - set -euo pipefail - version=$(grep -oP 'version "\K[^"]+' Casks/jdk26ea.rb) - if [[ -z "$version" ]]; then - echo "Failed to extract version from Casks/jdk26ea.rb" - exit 1 - fi - printf 'version=%s\n' "$version" >>"$GITHUB_OUTPUT" - - # Extract build number for changelog - build=$(echo "$version" | grep -oP '\+\K\d+') - if [[ -z "$build" || ! "$build" =~ ^[0-9]+$ ]]; then - echo "Failed to extract build number from version $version" - exit 1 - fi - printf 'build=%s\n' "$build" >>"$GITHUB_OUTPUT" - - - name: Check if tag exists - id: check_tag - run: | - if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then - echo "exists=true" >>"$GITHUB_OUTPUT" - else - echo "exists=false" >>"$GITHUB_OUTPUT" - fi - - - name: Get previous version - if: steps.check_tag.outputs.exists == 'false' - id: prev_version - run: | - prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - printf 'tag=%s\n' "$prev_tag" >>"$GITHUB_OUTPUT" - - if [[ -n "$prev_tag" ]]; then - prev_version="${prev_tag#v}" - printf 'version=%s\n' "$prev_version" >>"$GITHUB_OUTPUT" - else - echo "version=none" >>"$GITHUB_OUTPUT" - fi - - - name: Generate changelog - if: steps.check_tag.outputs.exists == 'false' - id: changelog - run: | - PREV_TAG="${{ steps.prev_version.outputs.tag }}" - - if [ -n "$PREV_TAG" ]; then - CHANGELOG=$(git log "${PREV_TAG}"..HEAD --pretty=format:"- %s (%h)" --no-merges) - else - CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges) - fi - - # Save to multiline output using delimiter - { - echo 'changelog<>"$GITHUB_OUTPUT" - - - name: Update README with current version - if: steps.check_tag.outputs.exists == 'false' - id: update_readme - env: - BUILD: ${{ steps.version.outputs.build }} - run: | - set -euo pipefail - python3 <<'PY' - import datetime - import os - import pathlib - import re - import sys - import urllib.request - - build = os.environ["BUILD"] - readme_path = pathlib.Path("README.md") - - try: - with urllib.request.urlopen("https://jdk.java.net/26/") as response: - page = response.read().decode("utf-8", "replace") - except Exception as exc: - print(f"Failed to fetch release page: {exc}", file=sys.stderr) - sys.exit(1) - - match = re.search(rf"Build {re.escape(build)} \((\d{{4}}/\d{{1,2}}/\d{{1,2}})\)", page) - if match: - year, month, day = match.group(1).split("/") - release_date = f"{int(year):04d}-{int(month):02d}-{int(day):02d}" - else: - release_date = datetime.date.today().isoformat() - - text = readme_path.read_text() - pattern = r"\*\*JDK 26 EA Build \d+\*\* \(Released: [0-9-]*\)" - replacement = f"**JDK 26 EA Build {build}** (Released: {release_date})" - new_text, count = re.subn(pattern, replacement, text, count=1) - if count != 1: - print("Failed to update README version banner", file=sys.stderr) - sys.exit(1) - - changed = new_text != text - if changed: - readme_path.write_text(new_text) - - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh: - fh.write(f"changed={'true' if changed else 'false'}\n") - fh.write(f"release_date={release_date}\n") - PY - - - name: Create README update pull request - if: steps.check_tag.outputs.exists == 'false' && steps.update_readme.outputs.changed == 'true' - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "docs: update README to JDK 26 EA Build ${{ steps.version.outputs.build }}" - title: "docs: update README to JDK 26 EA Build ${{ steps.version.outputs.build }}" - body: | - This PR updates README.md to reflect JDK 26 EA Build ${{ steps.version.outputs.build }} (released ${{ steps.update_readme.outputs.release_date }}). - branch: release/readme-${{ steps.version.outputs.build }} - delete-branch: true - - - name: Fetch release notes from OpenJDK - if: steps.check_tag.outputs.exists == 'false' - id: jdk_notes - run: | - # Try to fetch release notes (if available) - notes_url="https://jdk.java.net/26/" - - printf 'notes_url=%s\n' "$notes_url" >>"$GITHUB_OUTPUT" - - - name: Create Release - if: steps.check_tag.outputs.exists == 'false' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 - with: - tag_name: v${{ steps.version.outputs.version }} - name: JDK 26 EA Build ${{ steps.version.outputs.build }} - body: | - # JDK 26 Early Access Build ${{ steps.version.outputs.build }} - - OpenJDK 26 Early Access build from [jdk.java.net/26](${{ steps.jdk_notes.outputs.notes_url }}) - - ## Installation - - ### macOS & Linux - - ```bash - brew tap Artagon/jdk26ea - brew install --cask jdk26ea # macOS only - # or - brew install jdk26ea # macOS & Linux - ``` - - ## Supported Platforms - - - macOS ARM64 (Apple Silicon) - - macOS x64 (Intel) - - Linux ARM64 - - Linux x64 - - ## What's Changed - - ${{ steps.prev_version.outputs.version != 'none' && format('### Changes from {0}', steps.prev_version.outputs.version) || '### Initial Release' }} - - ${{ steps.changelog.outputs.changelog }} - - ## Checksums - - All SHA256 checksums are verified automatically. See the formula/cask files for details. - - ## Links - - - [OpenJDK JDK 26 EA Downloads](https://jdk.java.net/26/) - - [OpenJDK Project](https://openjdk.org/) - - --- - - **Note:** This is an early-access build and should not be used in production environments. - draft: false - prerelease: true diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml deleted file mode 100644 index b2e525b..0000000 --- a/.github/workflows/update.yml +++ /dev/null @@ -1,201 +0,0 @@ -name: Legacy JDK 26 EA verified update - -on: - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - update: - runs-on: ubuntu-latest - environment: - name: jdk-updates - url: https://github.com/${{ github.repository }}/pull/${{ steps.pr.outputs.pull-request-number }} - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Fetch latest JDK 26 EA - id: fetch - run: | - set -euo pipefail - page_file=$(mktemp) - curl --max-time 30 -fsSL https://jdk.java.net/26/ -o "$page_file" - - BUILD=$(grep -oE 'Build [0-9]+' "$page_file" | awk '{print $2}' | head -1) - if [[ -z "$BUILD" || ! "$BUILD" =~ ^[0-9]+$ ]]; then - echo "Failed to extract a valid build number" - exit 1 - fi - - VERSION="26-ea+${BUILD}" - - extract_url() { - local pattern=$1 - local label=$2 - local url - url=$(grep -oP "href=\"\\Khttps://[^\"]*${pattern}" "$page_file" | head -1) - if [[ -z "$url" ]]; then - echo "Failed to locate download URL for ${label}" - exit 1 - fi - if [[ ! "$url" =~ ^https://download\.java\.net/java/early_access/jdk26/${BUILD}/GPL/ ]]; then - echo "Unexpected download host for ${label}: $url" - exit 1 - fi - echo "$url" - } - - URL_MAC_ARM=$(extract_url "openjdk-26-ea\\+${BUILD}_macos-aarch64_bin\\.tar\\.gz" "macOS ARM") - URL_MAC_INTEL=$(extract_url "openjdk-26-ea\\+${BUILD}_macos-x64_bin\\.tar\\.gz" "macOS Intel") - URL_LINUX_ARM=$(extract_url "openjdk-26-ea\\+${BUILD}_linux-aarch64_bin\\.tar\\.gz" "Linux ARM") - URL_LINUX_X64=$(extract_url "openjdk-26-ea\\+${BUILD}_linux-x64_bin\\.tar\\.gz" "Linux x64") - - fetch_sha() { - local url=$1 - local label=$2 - local sha - sha=$(curl --max-time 30 -fsSL "${url}.sha256" | awk '{print $1}') - if [[ -z "$sha" || ! "$sha" =~ ^[0-9a-f]{64}$ ]]; then - echo "Invalid checksum for ${label}: $sha" - exit 1 - fi - echo "$sha" - } - - verify_tarball() { - local url=$1 - local expected=$2 - local label=$3 - local tmp - tmp=$(mktemp) - curl --max-time 300 -fsSL "$url" -o "$tmp" - local actual - actual=$(shasum -a 256 "$tmp" | awk '{print $1}') - rm -f "$tmp" - if [[ "$actual" != "$expected" ]]; then - echo "Checksum mismatch for ${label}" - exit 1 - fi - } - - SHA_MAC_ARM=$(fetch_sha "$URL_MAC_ARM" "macOS ARM") - SHA_MAC_INTEL=$(fetch_sha "$URL_MAC_INTEL" "macOS Intel") - SHA_LINUX_ARM=$(fetch_sha "$URL_LINUX_ARM" "Linux ARM") - SHA_LINUX_X64=$(fetch_sha "$URL_LINUX_X64" "Linux x64") - - verify_tarball "$URL_MAC_ARM" "$SHA_MAC_ARM" "macOS ARM" - verify_tarball "$URL_MAC_INTEL" "$SHA_MAC_INTEL" "macOS Intel" - verify_tarball "$URL_LINUX_ARM" "$SHA_LINUX_ARM" "Linux ARM" - verify_tarball "$URL_LINUX_X64" "$SHA_LINUX_X64" "Linux x64" - - rm -f "$page_file" - - { - printf 'version=%s\n' "$VERSION" - printf 'build=%s\n' "$BUILD" - printf 'cask_version=%s,%s\n' "$VERSION" "$BUILD" - printf 'url_mac_arm=%s\n' "$URL_MAC_ARM" - printf 'url_mac_intel=%s\n' "$URL_MAC_INTEL" - printf 'url_linux_arm=%s\n' "$URL_LINUX_ARM" - printf 'url_linux_x64=%s\n' "$URL_LINUX_X64" - printf 'sha_mac_arm=%s\n' "$SHA_MAC_ARM" - printf 'sha_mac_intel=%s\n' "$SHA_MAC_INTEL" - printf 'sha_linux_arm=%s\n' "$SHA_LINUX_ARM" - printf 'sha_linux_x64=%s\n' "$SHA_LINUX_X64" - } >>"$GITHUB_OUTPUT" - - - name: Update cask and formula files - env: - VERSION: ${{ steps.fetch.outputs.version }} - BUILD: ${{ steps.fetch.outputs.build }} - CASK_VERSION: ${{ steps.fetch.outputs.cask_version }} - URL_MAC_ARM: ${{ steps.fetch.outputs.url_mac_arm }} - URL_MAC_INTEL: ${{ steps.fetch.outputs.url_mac_intel }} - URL_LINUX_ARM: ${{ steps.fetch.outputs.url_linux_arm }} - URL_LINUX_X64: ${{ steps.fetch.outputs.url_linux_x64 }} - SHA_MAC_ARM: ${{ steps.fetch.outputs.sha_mac_arm }} - SHA_MAC_INTEL: ${{ steps.fetch.outputs.sha_mac_intel }} - SHA_LINUX_ARM: ${{ steps.fetch.outputs.sha_linux_arm }} - SHA_LINUX_X64: ${{ steps.fetch.outputs.sha_linux_x64 }} - run: | - set -euo pipefail - python3 <<'PY' - import os - import re - from pathlib import Path - - cask_path = Path("Casks/jdk26ea.rb") - cask_text = cask_path.read_text() - cask_text, replaced = re.subn( - r'version "[^"]+"', - f'version "{os.environ["CASK_VERSION"]}"', - cask_text, - count=1, - ) - if replaced != 1: - raise SystemExit("Failed to update cask version") - cask_text, replaced = re.subn( - r'sha256 arm:\s+"[0-9a-f]{64}",\s*\n\s*intel:\s+"[0-9a-f]{64}"', - f'sha256 arm: "{os.environ["SHA_MAC_ARM"]}",\n intel: "{os.environ["SHA_MAC_INTEL"]}"', - cask_text, - count=1, - ) - if replaced != 1: - raise SystemExit("Failed to update cask checksums") - cask_path.write_text(cask_text) - - formula_path = Path("Formula/jdk26ea.rb") - formula_text = formula_path.read_text() - formula_text, replaced = re.subn( - r'version "[^"]+"', - f'version "{os.environ["VERSION"]}"', - formula_text, - count=1, - ) - if replaced != 1: - raise SystemExit("Failed to update formula version") - - replacements = [ - (r'https://download\.java\.net/java/early_access/jdk26/\d+/GPL/openjdk-26-ea\+\d+_macos-aarch64_bin\.tar\.gz', os.environ["URL_MAC_ARM"]), - (r'https://download\.java\.net/java/early_access/jdk26/\d+/GPL/openjdk-26-ea\+\d+_macos-x64_bin\.tar\.gz', os.environ["URL_MAC_INTEL"]), - (r'https://download\.java\.net/java/early_access/jdk26/\d+/GPL/openjdk-26-ea\+\d+_linux-aarch64_bin\.tar\.gz', os.environ["URL_LINUX_ARM"]), - (r'https://download\.java\.net/java/early_access/jdk26/\d+/GPL/openjdk-26-ea\+\d+_linux-x64_bin\.tar\.gz', os.environ["URL_LINUX_X64"]), - ] - - for pattern, replacement in replacements: - formula_text, replaced = re.subn(pattern, replacement, formula_text, count=1) - if replaced != 1: - raise SystemExit(f"Failed to update formula URL for pattern {pattern}") - - sha_values = [ - os.environ["SHA_MAC_ARM"], - os.environ["SHA_MAC_INTEL"], - os.environ["SHA_LINUX_ARM"], - os.environ["SHA_LINUX_X64"], - ] - - for sha in sha_values: - formula_text, replaced = re.subn(r'sha256 "[0-9a-f]{64}"', f'sha256 "{sha}"', formula_text, count=1) - if replaced != 1: - raise SystemExit("Failed to update formula checksum") - - formula_path.write_text(formula_text) - PY - - - name: Create Pull Request - id: pr - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c - with: - commit-message: "Update JDK 26 EA to ${{ steps.fetch.outputs.version }}" - title: "Update JDK 26 EA to ${{ steps.fetch.outputs.version }}" - body: | - Auto-update JDK 26 EA to version ${{ steps.fetch.outputs.version }} - - - macOS ARM64: ${{ steps.fetch.outputs.url_mac_arm }} - - macOS x64: ${{ steps.fetch.outputs.url_mac_intel }} - - Linux ARM64: ${{ steps.fetch.outputs.url_linux_arm }} - - Linux x64: ${{ steps.fetch.outputs.url_linux_x64 }} - branch: update-jdk26-${{ steps.fetch.outputs.version }} - delete-branch: true diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 025a6e5..d8c383c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -25,7 +25,7 @@ jobs: - name: Lint workflows and shell scripts run: | actionlint .github/workflows/*.yml - shellcheck scripts/test.sh scripts/update.sh tests/formula_contract.bats + shellcheck scripts/test.sh tests/formula_contract.bats - name: Validate cask syntax run: | diff --git a/ABOUT.md b/ABOUT.md index bef4cc7..6f5809e 100644 --- a/ABOUT.md +++ b/ABOUT.md @@ -78,8 +78,8 @@ local refactoring does not perform that external change. Backward compatibility means: - Existing `brew tap Artagon/jdk26ea` commands remain valid. -- Existing `brew install jdk26ea` and `brew install --cask jdk26ea` commands - keep their current behavior. +- Existing `brew install jdk26ea` and `brew install --cask jdk26ea` commands, + tokens, and installation locations remain valid. - Existing installations are not moved or relinked. - No legacy package is silently converted into a Valhalla package. diff --git a/Casks/jdk26ea.rb b/Casks/jdk26ea.rb index 57a42e6..39664fb 100644 --- a/Casks/jdk26ea.rb +++ b/Casks/jdk26ea.rb @@ -1,15 +1,15 @@ cask "jdk26ea" do arch arm: "aarch64", intel: "x64" - version "26-ea+20,20" + version "26.0.2,10" # Installs to: /Library/Java/JavaVirtualMachines/jdk-26-ea.jdk # Supports: macOS ARM64 (Apple Silicon) and x64 (Intel) - sha256 arm: "dc75cdb507e47a66b0edc73d1cfc4a1c011078d5d0785c7660320d2e9c3e04d4", - intel: "5da4095d77d50eb19d8df7f0d128c16a6ff933d6cadc5cbf6fff1bf0530b6474" + sha256 arm: "c99b35ad3063ef555361a243c44280b048e24e3cbbc4a59ee3b368e5a8958f3a", + intel: "c258f17d4095c0cda0489d33fc4988d4be193a280b7e1f045e961699dedbfc65" - url "https://download.java.net/java/early_access/jdk26/#{version.csv.second}/GPL/openjdk-#{version.csv.first}_macos-#{arch}_bin.tar.gz" - name "JDK 26 EA" - desc "Early-Access JDK 26" + url "https://download.java.net/java/GA/jdk#{version.csv.first}/818d462d89b645c7a1aad49066c454e5/#{version.csv.second}/GPL/openjdk-#{version.csv.first}_macos-#{arch}_bin.tar.gz" + name "OpenJDK 26" + desc "OpenJDK 26 (legacy jdk26ea token)" homepage "https://jdk.java.net/26/" postflight do @@ -30,7 +30,7 @@ odie "Failed to remove existing JDK at #{jdk_target}" unless removal.success? end - ohai "Installing JDK 26 EA to #{jdk_target}" + ohai "Installing OpenJDK 26 to #{jdk_target}" install = system_command "/usr/bin/ditto", args: ["--noqtn", jdk_src.to_s, jdk_target.to_s], sudo: true diff --git a/Formula/jdk26ea.rb b/Formula/jdk26ea.rb index ec49bab..378becf 100644 --- a/Formula/jdk26ea.rb +++ b/Formula/jdk26ea.rb @@ -1,23 +1,23 @@ class Jdk26ea < Formula - desc "Early-Access JDK 26" + desc "OpenJDK 26 (legacy jdk26ea token)" homepage "https://jdk.java.net/26/" - version "26-ea+20" + version "26.0.2+10" on_macos do if Hardware::CPU.arm? - url "https://download.java.net/java/early_access/jdk26/20/GPL/openjdk-26-ea+20_macos-aarch64_bin.tar.gz" - sha256 "dc75cdb507e47a66b0edc73d1cfc4a1c011078d5d0785c7660320d2e9c3e04d4" + url "https://download.java.net/java/GA/jdk26.0.2/818d462d89b645c7a1aad49066c454e5/10/GPL/openjdk-26.0.2_macos-aarch64_bin.tar.gz" + sha256 "c99b35ad3063ef555361a243c44280b048e24e3cbbc4a59ee3b368e5a8958f3a" else - url "https://download.java.net/java/early_access/jdk26/20/GPL/openjdk-26-ea+20_macos-x64_bin.tar.gz" - sha256 "5da4095d77d50eb19d8df7f0d128c16a6ff933d6cadc5cbf6fff1bf0530b6474" + url "https://download.java.net/java/GA/jdk26.0.2/818d462d89b645c7a1aad49066c454e5/10/GPL/openjdk-26.0.2_macos-x64_bin.tar.gz" + sha256 "c258f17d4095c0cda0489d33fc4988d4be193a280b7e1f045e961699dedbfc65" end end on_linux do if Hardware::CPU.arm? - url "https://download.java.net/java/early_access/jdk26/20/GPL/openjdk-26-ea+20_linux-aarch64_bin.tar.gz" - sha256 "bf2a13c36da561391ccbda5d5d8dcce3963d35f2d5b0819a1fa725999f090aa4" + url "https://download.java.net/java/GA/jdk26.0.2/818d462d89b645c7a1aad49066c454e5/10/GPL/openjdk-26.0.2_linux-aarch64_bin.tar.gz" + sha256 "0ce6516c459e635d9f263f9b3492d83ec2c1ee26db128a6d904cae3d3096ceee" else - url "https://download.java.net/java/early_access/jdk26/20/GPL/openjdk-26-ea+20_linux-x64_bin.tar.gz" - sha256 "5a59bcbbbee3ef3870abde737d101b8688ff06144c853ff29ef6ac8247c96a87" + url "https://download.java.net/java/GA/jdk26.0.2/818d462d89b645c7a1aad49066c454e5/10/GPL/openjdk-26.0.2_linux-x64_bin.tar.gz" + sha256 "2da09e9db53e5c4f9eeec045f49e7d8fbcd8e4153edbf0c269f520ff82fd4198" end end def install @@ -33,6 +33,6 @@ class Hello { } JAVA system "#{bin}/javac", "--enable-preview", "--release", "26", "Hello.java" - assert_match(/26|26-ea/, shell_output("#{bin}/java --enable-preview Hello")) + assert_match(/26/, shell_output("#{bin}/java --enable-preview Hello")) end end diff --git a/README.md b/README.md index b43e12d..d585e50 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ new tokens. The existing `Artagon/jdk26ea` tap and packages remain compatible. | `openjdk-valhalla` | Alias of `openjdk-valhalla@28` | Current rolling Valhalla line | | `openjdk-valhalla@27` | OpenJDK tag `jep401ea3_branched` at `f9799f4c1a35` | Source line for the published JEP 401 EA3 milestone | | `openjdk-valhalla@28` | Valhalla `lworld` commit `f181286389fa` | Reviewed JDK 28 EA source snapshot | -| `jdk26ea` | Existing OpenJDK 26 EA binaries | Backward compatibility; not a Valhalla release | +| `jdk26ea` | OpenJDK 26.0.2 GA binaries | Backward-compatible token; not a Valhalla release | There is no `openjdk-valhalla@26` formula because no verified Project Valhalla 26 milestone is available from the current official release surface. The @@ -90,8 +90,9 @@ brew install --cask jdk26ea Existing formula installs remain under the Homebrew prefix. Existing cask installs remain at `/Library/Java/JavaVirtualMachines/jdk-26-ea.jdk`. -The compatibility surface is frozen to the last published JDK 26 EA build. New -Valhalla users should use versioned `openjdk-valhalla@N` formulae. +The compatibility surface is frozen to the official JDK 26.0.2 GA archives. +The token and cask installation path remain unchanged. New Valhalla users +should use versioned `openjdk-valhalla@N` formulae. ## Bottles diff --git a/SECURITY.md b/SECURITY.md index 5f52f3f..3f946e9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -72,8 +72,8 @@ Normal pull-request validation does not build OpenJDK. - Formula source archives and boot JDK resources use immutable URLs and SHA-256 checksums. -- Legacy binary updates validate the expected download host, checksum format, - and downloaded file before opening a pull request. +- Legacy compatibility binaries are frozen to official JDK 26.0.2 GA archive + URLs and their published SHA-256 checksums. - Formula and cask changes run Ruby syntax checks, Homebrew style/audit checks, and compatibility tests. - Workflow YAML runs actionlint, ShellCheck, and executable security-contract diff --git a/scripts/test.sh b/scripts/test.sh index 2349833..5e2545e 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Run local validation for the JDK 26 EA tap. +# Run local validation for the legacy JDK 26 compatibility tap. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" diff --git a/scripts/update.sh b/scripts/update.sh deleted file mode 100755 index 3b22244..0000000 --- a/scripts/update.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env bash -# Update JDK 26 EA cask and formula to the latest build -set -euo pipefail - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# Directories -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -FORMULA="$ROOT/Formula/jdk26ea.rb" -CASK="$ROOT/Casks/jdk26ea.rb" - -# JDK page URL -JDK_PAGE="https://jdk.java.net/26/" - -log_info() { - echo -e "${GREEN}[INFO]${NC} $*" -} - -log_warn() { - echo -e "${YELLOW}[WARN]${NC} $*" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $*" -} - -# Fetch the JDK page and extract build number -log_info "Fetching latest JDK 26 EA build information from $JDK_PAGE" -page_content=$(curl -fsSL "$JDK_PAGE" 2>/dev/null || { - log_error "Failed to fetch JDK page" - exit 1 -}) - -# Extract build number from page -build_number=$(printf '%s\n' "$page_content" | awk ' - match($0, /Build ([0-9]+)/, m) { - print m[1] - exit - } -') - -if [[ -z "$build_number" ]]; then - log_error "Could not extract build number from JDK page" - exit 1 -fi - -cask_version="26-ea+${build_number},${build_number}" -version="26-ea+${build_number}" -log_info "Latest build: $version (cask version key: $cask_version)" - -# Get current version from cask -current_version=$(sed -n 's/^ version "\(.*\)"/\1/p' "$CASK" 2>/dev/null | head -1) -current_version=${current_version:-unknown} -log_info "Current version: $current_version" - -if [[ "$cask_version" == "$current_version" ]]; then - log_info "Already at latest version: $cask_version" - exit 0 -fi - -# Define download URLs -base_url="https://download.java.net/java/early_access/jdk26/${build_number}/GPL" -mac_arm_url="${base_url}/openjdk-${version}_macos-aarch64_bin.tar.gz" -mac_x64_url="${base_url}/openjdk-${version}_macos-x64_bin.tar.gz" -linux_arm_url="${base_url}/openjdk-${version}_linux-aarch64_bin.tar.gz" -linux_x64_url="${base_url}/openjdk-${version}_linux-x64_bin.tar.gz" - -# Function to get SHA256 from remote .sha256 file -get_remote_sha256() { - local url="$1" - local sha_url="${url}.sha256" - local sha - - sha=$(curl -fsSL "$sha_url" 2>/dev/null | awk '{print $1}') - - if [[ -z "$sha" || ! "$sha" =~ ^[a-f0-9]{64}$ ]]; then - log_error "Failed to get valid SHA256 for $url" - return 1 - fi - - echo "$sha" -} - -# Fetch all SHA256 checksums -log_info "Fetching SHA256 checksums..." -log_info " - mac_arm" -mac_arm_sha=$(get_remote_sha256 "$mac_arm_url") || exit 1 -log_info " - mac_x64" -mac_x64_sha=$(get_remote_sha256 "$mac_x64_url") || exit 1 -log_info " - linux_arm" -linux_arm_sha=$(get_remote_sha256 "$linux_arm_url") || exit 1 -log_info " - linux_x64" -linux_x64_sha=$(get_remote_sha256 "$linux_x64_url") || exit 1 - -log_info "All checksums fetched successfully" - -# Backup files -log_info "Creating backups..." -cp "$CASK" "${CASK}.backup" -cp "$FORMULA" "${FORMULA}.backup" - -# Update CASK -log_info "Updating Cask..." - -# Update version -sed -i.tmp "s/version \".*\"/version \"$cask_version\"/" "$CASK" - -# Update checksums in cask -awk -v arm="$mac_arm_sha" -v intel="$mac_x64_sha" ' - /sha256 arm:/ { - print " sha256 arm: \"" arm "\"," - print " intel: \"" intel "\"" - next - } - { print } -' "$CASK" > "${CASK}.new" && mv "${CASK}.new" "$CASK" - -# Remove sed temp files -rm -f "${CASK}.tmp" - -# Update FORMULA -log_info "Updating Formula..." - -# Update version -sed -i.tmp "s/version \".*\"/version \"$version\"/" "$FORMULA" - -# Update all URLs -sed -i.tmp \ - -e "s|https://download.java.net/java/early_access/jdk26/[0-9]*/GPL/openjdk-.*_macos-aarch64_bin.tar.gz|${mac_arm_url}|g" \ - -e "s|https://download.java.net/java/early_access/jdk26/[0-9]*/GPL/openjdk-.*_macos-x64_bin.tar.gz|${mac_x64_url}|g" \ - -e "s|https://download.java.net/java/early_access/jdk26/[0-9]*/GPL/openjdk-.*_linux-aarch64_bin.tar.gz|${linux_arm_url}|g" \ - -e "s|https://download.java.net/java/early_access/jdk26/[0-9]*/GPL/openjdk-.*_linux-x64_bin.tar.gz|${linux_x64_url}|g" \ - "$FORMULA" - -# Update checksums in formula -awk -v mac_arm="$mac_arm_sha" \ - -v mac_x64="$mac_x64_sha" \ - -v linux_arm="$linux_arm_sha" \ - -v linux_x64="$linux_x64_sha" ' - BEGIN { in_macos=0; in_linux=0; in_arm_block=0 } - - /on_macos do/ { in_macos=1; in_linux=0; next_is_macos=1 } - /on_linux do/ { in_linux=1; in_macos=0; next_is_linux=1 } - - /if Hardware::CPU\.arm\?/ { in_arm_block=1 } - /else$/ { in_arm_block=0 } - /^ end$/ { - if (in_macos || in_linux) { - in_macos=0 - in_linux=0 - } - } - - /sha256/ { - if (in_macos && in_arm_block) { - gsub(/sha256 ".*"/, "sha256 \"" mac_arm "\"") - } - else if (in_macos && !in_arm_block) { - gsub(/sha256 ".*"/, "sha256 \"" mac_x64 "\"") - } - else if (in_linux && in_arm_block) { - gsub(/sha256 ".*"/, "sha256 \"" linux_arm "\"") - } - else if (in_linux && !in_arm_block) { - gsub(/sha256 ".*"/, "sha256 \"" linux_x64 "\"") - } - } - - {print} -' "$FORMULA" > "${FORMULA}.new" && mv "${FORMULA}.new" "$FORMULA" - -# Remove sed temp files -rm -f "${FORMULA}.tmp" - -# Validate Ruby syntax -log_info "Validating Ruby syntax..." - -if ! ruby -c "$CASK" > /dev/null 2>&1; then - log_error "Cask syntax validation failed!" - log_warn "Restoring backup..." - mv "${CASK}.backup" "$CASK" - mv "${FORMULA}.backup" "$FORMULA" - exit 1 -fi - -if ! ruby -c "$FORMULA" > /dev/null 2>&1; then - log_error "Formula syntax validation failed!" - log_warn "Restoring backup..." - mv "${CASK}.backup" "$CASK" - mv "${FORMULA}.backup" "$FORMULA" - exit 1 -fi - -# Clean up backups -rm -f "${CASK}.backup" "${FORMULA}.backup" - -# Summary -log_info "Successfully updated to $version" -echo "" -echo "Summary of changes:" -echo " Previous cask version: $current_version" -echo " New cask version: $cask_version" -echo " Formula version updated: $version" -echo "" -echo "Updated files:" -echo " - $CASK" -echo " - $FORMULA" -echo "" -echo "Checksums:" -echo " macOS ARM64: $mac_arm_sha" -echo " macOS x64: $mac_x64_sha" -echo " Linux ARM64: $linux_arm_sha" -echo " Linux x64: $linux_x64_sha" -echo "" -log_info "Run 'git diff' to review changes" -log_info "Run 'git add . && git commit -m \"Update to JDK $version\"' to commit" diff --git a/tests/formula_contract.bats b/tests/formula_contract.bats index 04e2227..ef010ce 100644 --- a/tests/formula_contract.bats +++ b/tests/formula_contract.bats @@ -33,3 +33,20 @@ setup() { [ "${status}" -eq 0 ] [ "${output}" = "Syntax OK" ] } + +@test "legacy jdk26ea token uses current official JDK 26 artifacts" { + local formula_contents cask_contents release_root + + formula_contents="$(<"${REPO_ROOT}/Formula/jdk26ea.rb")" + cask_contents="$(<"${REPO_ROOT}/Casks/jdk26ea.rb")" + release_root="https://download.java.net/java/GA/jdk26.0.2/818d462d89b645c7a1aad49066c454e5/10/GPL" + + [[ "${formula_contents}" == *'version "26.0.2+10"'* ]] + [[ "${formula_contents}" == *"${release_root}/openjdk-26.0.2_linux-aarch64_bin.tar.gz"* ]] + [[ "${formula_contents}" == *"${release_root}/openjdk-26.0.2_linux-x64_bin.tar.gz"* ]] + [[ "${formula_contents}" == *"${release_root}/openjdk-26.0.2_macos-aarch64_bin.tar.gz"* ]] + [[ "${formula_contents}" == *"${release_root}/openjdk-26.0.2_macos-x64_bin.tar.gz"* ]] + + [[ "${cask_contents}" == *'version "26.0.2,10"'* ]] + [[ "${cask_contents}" == *'download.java.net/java/GA/jdk#{version.csv.first}'* ]] +} diff --git a/tests/workflow_security_test.rb b/tests/workflow_security_test.rb index 8aea2e1..eda4b85 100644 --- a/tests/workflow_security_test.rb +++ b/tests/workflow_security_test.rb @@ -75,7 +75,7 @@ def test_validation_gates_workflow_and_shell_lint assert_includes install.fetch("run"), "actionlint" assert_includes install.fetch("run"), "shellcheck" assert_includes lint.fetch("run"), "actionlint .github/workflows/*.yml" - assert_includes lint.fetch("run"), "shellcheck scripts/test.sh scripts/update.sh tests/formula_contract.bats" + assert_includes lint.fetch("run"), "shellcheck scripts/test.sh tests/formula_contract.bats" end def test_every_action_reference_is_an_immutable_commit