diff --git a/.github/actions/install-cmake/action.yml b/.github/actions/install-cmake/action.yml new file mode 100644 index 00000000..01cc3004 --- /dev/null +++ b/.github/actions/install-cmake/action.yml @@ -0,0 +1,50 @@ +name: 'Install CMake' +description: 'Download and install CMake' + +inputs: + cmake-version: + description: "CMake version (e.g. '3.30.2')" + required: true + hash: + description: "Expected SHA-256 hash of the target archive (Required)." + required: true + install_dir: + description: "Location where to install CMake" + required: false + default: "" + +outputs: + cmake-dir: + description: "Directory containing the CMake binaries" + value: ${{ steps.install.outputs.cmake-dir }} + cmake: + description: "Path to the installed CMake binary" + value: ${{ steps.install.outputs.cmake-path }} + ctest: + description: "Path to the installed CTest binary" + value: ${{ steps.install.outputs.ctest-path }} + +runs: + using: "composite" + steps: + - name: Run CMake Installer + id: install + shell: bash + env: + CMAKE_VERSION: ${{ inputs.cmake-version }} + CMAKE_SHA256: ${{ inputs.hash }} + CMAKE_INSTALL_DIR: ${{ inputs.install_dir }} + RAW_ACTION_PATH: ${{ github.action_path }} + run: | + # Resolve path whether running on the host runner or inside a Docker container + SCRIPT_PATH="${RAW_ACTION_PATH}/install-cmake.sh" + if [ ! -f "${SCRIPT_PATH}" ]; then + CONTAINER_PATH="$(echo "${RAW_ACTION_PATH}" | sed 's|^/home/runner/work|/__w|')" + if [ -f "${CONTAINER_PATH}/install-cmake.sh" ]; then + SCRIPT_PATH="${CONTAINER_PATH}/install-cmake.sh" + elif [ -f "${GITHUB_WORKSPACE}/.github/actions/install-cmake/install-cmake.sh" ]; then + SCRIPT_PATH="${GITHUB_WORKSPACE}/.github/actions/install-cmake/install-cmake.sh" + fi + fi + + bash "${SCRIPT_PATH}" diff --git a/.github/actions/install-cmake/install-cmake.sh b/.github/actions/install-cmake/install-cmake.sh new file mode 100755 index 00000000..8ac81f34 --- /dev/null +++ b/.github/actions/install-cmake/install-cmake.sh @@ -0,0 +1,102 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift.org open source project +## +## Copyright (c) 2026 Apple Inc. and the Swift project authors +## Licensed under Apache License v2.0 with Runtime Library Exception +## +## See https://swift.org/LICENSE.txt for license information +## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +## +##===----------------------------------------------------------------------===## +set -euo pipefail + +VERSION="${CMAKE_VERSION:?CMAKE_VERSION is required}" +EXPECTED_SHA="${CMAKE_SHA256:?CMAKE_SHA256 is required}" +OS="${RUNNER_OS:?RUNNER_OS is required}" +ARCH="${RUNNER_ARCH:?RUNNER_ARCH is required}" +TEMP_DIR="${RUNNER_TEMP:?RUNNER_TEMP is required}" + +# Normalize hash to lowercase and strip whitespace +EXPECTED_SHA=$(echo "${EXPECTED_SHA}" | tr '[:upper:]' '[:lower:]' | xargs) + +# 1. Determine destination directory (fallback to $RUNNER_TEMP/cmake- if unset/empty) +DEST_DIR="${CMAKE_INSTALL_DIR:-}" +if [ -z "${DEST_DIR}" ]; then + DEST_DIR="${TEMP_DIR}/cmake-${VERSION}" +fi + +# 2. Determine archive filename, binary subpath, and binary extension +BIN_EXT="" +case "${OS}" in + Linux) + case "${ARCH}" in + X64) ARCH_NAME="x86_64" ;; + ARM64) ARCH_NAME="aarch64" ;; + *) echo "::error::Unsupported Linux architecture: ${ARCH}"; exit 1 ;; + esac + ARCHIVE="cmake-${VERSION}-linux-${ARCH_NAME}.tar.gz" + BIN_SUBPATH="bin" + ;; + macOS) + ARCHIVE="cmake-${VERSION}-macos-universal.tar.gz" + BIN_SUBPATH="CMake.app/Contents/bin" + ;; + Windows) + case "${ARCH}" in + X64) ARCH_NAME="x86_64" ;; + ARM64) ARCH_NAME="arm64" ;; + *) echo "::error::Unsupported Windows architecture: ${ARCH}"; exit 1 ;; + esac + ARCHIVE="cmake-${VERSION}-windows-${ARCH_NAME}.zip" + BIN_SUBPATH="bin" + BIN_EXT=".exe" + ;; + *) + echo "::error::Unsupported OS: ${OS}" + exit 1 + ;; +esac + +ARCHIVE_URL="https://github.com/Kitware/CMake/releases/download/v${VERSION}/${ARCHIVE}" +ARCHIVE_PATH="${TEMP_DIR}/${ARCHIVE}" + +# 3. Download +echo "==> Downloading ${ARCHIVE_URL}..." +curl -fsSL --retry 3 -o "${ARCHIVE_PATH}" "${ARCHIVE_URL}" + +# 4. Verify SHA-256 directly in one step +echo "==> Verifying SHA-256..." +CHECK_CMD=$(command -v sha256sum || echo "shasum -a 256") +echo "${EXPECTED_SHA} ${ARCHIVE_PATH}" | ${CHECK_CMD} -c - + +# 5. Extract into destination directory +echo "==> Extracting into ${DEST_DIR}..." +mkdir -p "${DEST_DIR}" + +if [[ "${ARCHIVE}" == *.tar.gz ]]; then + tar -xzf "${ARCHIVE_PATH}" -C "${DEST_DIR}" --strip-components=1 +elif [[ "${ARCHIVE}" == *.zip ]]; then + UNZIP_TEMP="${TEMP_DIR}/unzip_tmp" + mkdir -p "${UNZIP_TEMP}" + unzip -q -o "${ARCHIVE_PATH}" -d "${UNZIP_TEMP}" + TOP_DIR=$(find "${UNZIP_TEMP}" -mindepth 1 -maxdepth 1 -type d) + mv "${TOP_DIR}"/* "${DEST_DIR}/" + rm -rf "${UNZIP_TEMP}" +fi +rm -f "${ARCHIVE_PATH}" + +# 6. Set PATH and GITHUB_OUTPUT +CMAKE_BIN_DIR="${DEST_DIR}/${BIN_SUBPATH}" +echo "${CMAKE_BIN_DIR}" >> "${GITHUB_PATH}" + +if [ -n "${GITHUB_OUTPUT:-}" ]; then + { + echo "cmake-dir=${CMAKE_BIN_DIR}" + echo "cmake-path=${CMAKE_BIN_DIR}/cmake${BIN_EXT}" + echo "ctest-path=${CMAKE_BIN_DIR}/ctest${BIN_EXT}" + } >> "${GITHUB_OUTPUT}" +fi + +echo "==> CMake ${VERSION} successfully installed at ${CMAKE_BIN_DIR}" diff --git a/.github/actions/install-cmake/readme.md b/.github/actions/install-cmake/readme.md new file mode 100644 index 00000000..4f22fc1c --- /dev/null +++ b/.github/actions/install-cmake/readme.md @@ -0,0 +1,47 @@ +# Install CMake + +Install an arbitrary version of CMake. + +Inputs: + - cmake-version: Version of CMake to install (e.g. 3.30.2) + - hash: sha256 of the downloaded artifact (.zip or .tar.gz) + - install_dir: (optional) location where to install CMake + +Outputs: + - cmake-dir: Directory containing the CMake binaries + - cmake: Path to the installed CMake binary + - ctest: Path to the installed CTest binary + + +## Usage + +```yaml +... + +steps: + + ... + + - name: Install CMake + id: install-cmake + uses: swiftlang/github-workflows/.github/actions/install-cmake@ + with: + cmake-version: 4.0.3 + hash: 585ae9e013107bc8e7c7c9ce872cbdcbdff569e675b07ef57aacfb88c886faac + + - name: Run Build + shell: bash + env: + CMAKE: ${{ steps.install-cmake.outputs.cmake }} + run: | + "$CMAKE" -G Ninja -B build -S -DCMAKE_BUILD_TYPE=Release + + - name: Run Tests + shell: bash + env: + CTEST: ${{ steps.install-cmake.outputs.ctest }} + run: | + "$CTEST" --output-on-failure --test-timeout 150 --test-dir build -j + + ... +``` diff --git a/.github/workflows/install_cmake_test.yml b/.github/workflows/install_cmake_test.yml new file mode 100644 index 00000000..f1f5ee9e --- /dev/null +++ b/.github/workflows/install_cmake_test.yml @@ -0,0 +1,178 @@ +name: Install CMake Test + +permissions: + contents: read + +on: + workflow_call: + inputs: + cmake_version: + type: string + description: "CMake version to install and verify" + default: "4.0.3" + linux_x86_64_hash: + type: string + description: "SHA-256 of cmake--linux-x86_64.tar.gz" + default: "585ae9e013107bc8e7c7c9ce872cbdcbdff569e675b07ef57aacfb88c886faac" + linux_aarch64_hash: + type: string + description: "SHA-256 of cmake--linux-aarch64.tar.gz" + default: "391da1544ef50ac31300841caaf11db4de3976cdc4468643272e44b3f4644713" + macos_hash: + type: string + description: "SHA-256 of cmake--macos-universal.tar.gz" + default: "4e85de4daf1c3e82d7dc6b8ba5683972944b466343aeb9c327a742437bb3ce9a" + windows_x86_64_hash: + type: string + description: "SHA-256 of cmake--windows-x86_64.zip" + default: "b59a31dfbfa376a4aaea9ff560ff2b29f78ee5f9fb15447fc71ae7bf9fea9379" + windows_arm64_hash: + type: string + description: "SHA-256 of cmake--windows-arm64.zip" + default: "86ccd6485bbd4bb41a1a858db397be5bca5e0de96858bf8dbba7a64407bd6c00" + workflow_dispatch: + +jobs: + linux: + name: "Linux (${{ matrix.arch }})" + strategy: + fail-fast: false + matrix: + include: + - arch: X64 + runner: ubuntu-24.04 + hash: ${{ inputs.linux_x86_64_hash }} + - arch: ARM64 + runner: ubuntu-24.04-arm + hash: ${{ inputs.linux_aarch64_hash }} + runs-on: ${{ matrix.runner }} + container: + image: swift:6.2-jammy + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Install prerequisites + shell: bash + run: | + apt-get update + apt-get install -y curl + - name: Install CMake + id: install + uses: ./.github/actions/install-cmake + with: + cmake-version: ${{ inputs.cmake_version }} + hash: ${{ matrix.hash }} + - name: Verify CMake installation + shell: bash + env: + EXPECTED_VERSION: ${{ inputs.cmake_version }} + CMAKE: ${{ steps.install.outputs.cmake }} + CTEST: ${{ steps.install.outputs.ctest }} + CMAKE_DIR: ${{ steps.install.outputs.cmake-dir }} + run: | + set -euo pipefail + # Outputs point at real, executable files + test -x "${CMAKE}" + test -x "${CTEST}" + test -d "${CMAKE_DIR}" + # Explicit output paths run and report the expected version + "${CMAKE}" --version | grep -q "${EXPECTED_VERSION}" + "${CTEST}" --version + # GITHUB_PATH was updated: bare `cmake` resolves in this later step + cmake --version | grep -q "${EXPECTED_VERSION}" + + macos: + name: "macOS (ARM64)" + runs-on: [self-hosted, macos, tahoe, ARM64] + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Install CMake + id: install + uses: ./.github/actions/install-cmake + with: + cmake-version: ${{ inputs.cmake_version }} + hash: ${{ inputs.macos_hash }} + - name: Verify CMake installation + shell: bash + env: + EXPECTED_VERSION: ${{ inputs.cmake_version }} + CMAKE: ${{ steps.install.outputs.cmake }} + CTEST: ${{ steps.install.outputs.ctest }} + CMAKE_DIR: ${{ steps.install.outputs.cmake-dir }} + run: | + set -euo pipefail + test -x "${CMAKE}" + test -x "${CTEST}" + test -d "${CMAKE_DIR}" + "${CMAKE}" --version | grep -q "${EXPECTED_VERSION}" + "${CTEST}" --version + cmake --version | grep -q "${EXPECTED_VERSION}" + + windows: + name: "Windows (${{ matrix.runner }})" + strategy: + fail-fast: false + matrix: + include: + - runner: windows-2022 + hash: ${{ inputs.windows_x86_64_hash }} + - runner: windows-11-arm + hash: ${{ inputs.windows_arm64_hash }} + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Install CMake + id: install + uses: ./.github/actions/install-cmake + with: + cmake-version: ${{ inputs.cmake_version }} + hash: ${{ matrix.hash }} + - name: Verify CMake installation + shell: bash + env: + EXPECTED_VERSION: ${{ inputs.cmake_version }} + CMAKE: ${{ steps.install.outputs.cmake }} + CTEST: ${{ steps.install.outputs.ctest }} + CMAKE_DIR: ${{ steps.install.outputs.cmake-dir }} + run: | + set -euo pipefail + test -x "${CMAKE}" + test -x "${CTEST}" + test -d "${CMAKE_DIR}" + "${CMAKE}" --version | grep -q "${EXPECTED_VERSION}" + "${CTEST}" --version + cmake --version | grep -q "${EXPECTED_VERSION}" + + bad_hash: + name: "Negative test (wrong hash)" + runs-on: ubuntu-24.04 + container: + image: swift:6.2-jammy + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Install prerequisites + shell: bash + run: | + apt-get update + apt-get install -y curl + - name: Install CMake with a wrong hash (expected to fail) + id: install + continue-on-error: true + uses: ./.github/actions/install-cmake + with: + cmake-version: ${{ inputs.cmake_version }} + hash: "0000000000000000000000000000000000000000000000000000000000000000" + - name: Assert the install failed + shell: bash + env: + OUTCOME: ${{ steps.install.outcome }} + run: | + set -euo pipefail + if [ "${OUTCOME}" != "failure" ]; then + echo "::error::Expected install to fail on hash mismatch, but outcome was '${OUTCOME}'" + exit 1 + fi + echo "Install correctly failed on SHA-256 mismatch." diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6262e3e0..bcd264ae 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -110,6 +110,10 @@ jobs: project: "swift" workflow_test_enabled: true + install_cmake: + name: Install CMake + uses: ./.github/workflows/install_cmake_test.yml + soundness-docs: name: "Soundness - Docs (override_target_name: ${{ matrix.override_target_name }} ; post command: ${{ matrix.post_command }})" strategy: