diff --git a/.github/workflows/cross-library.yml b/.github/workflows/cross-library.yml new file mode 100644 index 0000000000..8f4e063129 --- /dev/null +++ b/.github/workflows/cross-library.yml @@ -0,0 +1,279 @@ +name: cross-library (reusable) + +# Reusable engine for compile-testing a downstream wolfSSL product against the +# wolfSSL in this checkout. It builds wolfSSL once from this checkout (the PR +# merge commit) with the flags a product needs, then compiles the product +# against that install at both the product's default-branch HEAD and its highest +# release tag (compile-only, no `make check`). +# +# The wolfSSL build runs once and is shared: the build-wolfssl job installs +# wolfSSL and uploads it as an artifact, and the compile matrix (head, latest) +# downloads that artifact instead of rebuilding. This halves the wolfSSL builds +# per product from two to one. + +on: + workflow_call: + inputs: + product: + description: 'Short product label (used for the job name)' + required: true + type: string + repo: + description: 'Product source: "owner/repo" shorthand or a full git URL' + required: true + type: string + wolfssl_configure: + description: 'Configure flags for the local wolfSSL build' + required: true + type: string + product_configure: + description: "The product's own ./configure flags" + required: false + default: '' + type: string + script: + description: 'Build script name under .github/workflows/cross-library/scripts/' + required: true + type: string + apt_packages: + description: 'Extra apt packages needed to build the product' + required: false + default: '' + type: string + container: + description: 'Container image to build in (e.g. ubuntu:24.04, debian:13)' + required: false + default: 'ubuntu:24.04' + type: string + +jobs: + # Build wolfSSL once and publish the install dir. Both compile legs (head, + # latest) consume it, so this runs a single time per product instead of once + # per leg. + build-wolfssl: + name: Build wolfSSL (${{ inputs.product }}) + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + container: + image: ${{ inputs.container }} + timeout-minutes: 20 + steps: + # Minimal-image containers ship without git/toolchain; install them + # before checkout. Product-specific extras come from apt_packages. + - name: Install build tools + run: | + set -eux + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends \ + build-essential autoconf automake libtool pkg-config \ + git ca-certificates ${{ inputs.apt_packages }} + + # Building only needs the commit under test, not history. The break check + # that needs history runs in the compile job, not here. + - name: Checkout wolfSSL + uses: actions/checkout@v5 + with: + fetch-depth: 1 + fetch-tags: false + + # Container job: the bind-mounted workspace is owned by a different uid, + # so mark it safe or git refuses to operate on it ("dubious ownership"). + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + # Build wolfSSL and install to a local dir. The configure flags go through + # an env var (not inline ${{ }}) so a quoted CFLAGS/C_EXTRA_FLAGS group + # survives GitHub's expansion intact; the string is trusted caller input + # that build-wolfssl.sh eval's. + - name: Build and install wolfSSL + env: + WOLFSSL_CONFIGURE: ${{ inputs.wolfssl_configure }} + run: | + .github/workflows/cross-library/scripts/build-wolfssl.sh \ + "$GITHUB_WORKSPACE" \ + "$GITHUB_WORKSPACE/wolfssl-install" \ + "$WOLFSSL_CONFIGURE" + + # Pack as a tar so libtool symlinks, exec bits, and the absolute-path + # .la/.pc files survive the artifact round-trip. The compile job unpacks to + # the same $GITHUB_WORKSPACE path, so those baked-in paths stay valid. + - name: Pack the wolfSSL install + run: tar -C "$GITHUB_WORKSPACE" -czf wolfssl-install.tar.gz wolfssl-install + + - name: Upload the wolfSSL install + uses: actions/upload-artifact@v4 + with: + name: wolfssl-install-${{ inputs.product }} + path: wolfssl-install.tar.gz + retention-days: 1 + + # Compile the product against the shared wolfSSL, once per ref_mode: + # head -> HEAD of the product's default branch (master/main, auto-detected) + # latest -> the product's highest version tag (polled at run time) + compile: + name: Compile ${{ inputs.product }} (${{ matrix.ref_mode }}) + needs: build-wolfssl + runs-on: ubuntu-24.04 + container: + image: ${{ inputs.container }} + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + ref_mode: [ head, latest ] + steps: + # Minimal-image containers ship without git/toolchain; install them + # before checkout. Product-specific extras come from apt_packages. + - name: Install build tools + run: | + set -eux + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends \ + build-essential autoconf automake libtool pkg-config \ + git ca-certificates ${{ inputs.apt_packages }} + + # This job does not build wolfSSL, but the latest leg still checks out + # wolfSSL history because check-break.sh scans commit messages here. The + # head leg never waives a break, so a depth-1 checkout is enough for it. + - name: Checkout wolfSSL + uses: actions/checkout@v5 + with: + fetch-depth: 1 + fetch-tags: false + + # This is a container job: the workspace is a host bind-mount owned by a + # different uid than the (root) container user, so git refuses to operate + # on it ("detected dubious ownership") unless it is marked safe. actions/ + # checkout marks it safe only under its own *temporary* HOME, which our + # run: steps (HOME=/github/home) do not inherit, so check-break.sh's + # `git tag`/`git log` would fail and, with stderr hidden, look like "no + # break declared". Mark it safe under this HOME for all following steps. + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + # The `latest` leg is the only one that reads wolfSSL history: it needs the + # commit messages check-break.sh scans (the last two release cycles) plus + # the two newest release tags, and no file contents, so --filter=tree:0 + # fetches commits+tags only and turns a 912 MiB full clone into ~1 MiB on + # top of the source. The exclude boundary is the THIRD-newest release, not + # the scan base, because --shallow-exclude severs the ref it names and + # severing the scan base would drop it from `git tag --merged HEAD` and + # silently halve the window. Tags come in by explicit refspec (never + # --tags, which re-pulls every tag's history), and note a server without + # uploadpack.allowFilter silently ignores the filter and clones in full. + - name: Deepen wolfSSL history for the break check + if: ${{ matrix.ref_mode == 'latest' }} + shell: bash + run: | + set -euo pipefail + + # Release-tag selection MUST agree with check-break.sh's + # `--list 'v*-stable' --sort=-v:refname`. `sort -Vr` matches git's + # -v:refname on this tag set (verified incl. double-digit minors, + # e.g. v5.9.10 > v5.9.2). The assertion below catches any drift. + mapfile -t T < <(git ls-remote --tags --refs origin 'v*-stable' \ + | sed 's#.*refs/tags/##' | sort -Vr) + echo "Newest release tags: ${T[*]:0:3}" + + if [ "${#T[@]}" -lt 3 ]; then + echo "Fewer than three release tags; falling back to full history." + git fetch --unshallow --filter=tree:0 --tags origin + else + # Fetch by ref (not raw SHA) so this does not depend on the server + # allowing reachable-SHA1-in-want. + git fetch --no-tags --filter=tree:0 --shallow-exclude="${T[2]}" origin \ + "$GITHUB_REF" \ + "refs/tags/${T[0]}:refs/tags/${T[0]}" \ + "refs/tags/${T[1]}:refs/tags/${T[1]}" + fi + + # Fail LOUD if this step and check-break.sh disagree on the scan base. + # check-break.sh:61 falls back to the NEWEST tag when the second line + # is missing, which would quietly halve the window rather than error. + rel="$(git tag --merged HEAD --sort=-v:refname --list 'v*-stable')" + base="$(printf '%s\n' "$rel" | sed -n '2p')" + echo "Deepened to $(git rev-list --count HEAD) commits; scan base='${base}'" + if [ -z "$base" ]; then + echo "::error::Deepen produced no usable scan base; check-break.sh would scan the wrong window." + printf 'Release tags merged into HEAD: %s\n' "${rel:-}" + exit 1 + fi + + - name: Download the wolfSSL install + uses: actions/download-artifact@v4 + with: + name: wolfssl-install-${{ inputs.product }} + + # Unpack to the same path wolfSSL was built at, keeping the .la/.pc + # absolute paths valid. + - name: Unpack the wolfSSL install + run: tar -C "$GITHUB_WORKSPACE" -xzf wolfssl-install.tar.gz + + # Resolve the concrete ref to compile: the highest tag (latest) or the + # default branch (head). Used both for the build and the break check. + - name: Resolve product ref + id: ref + env: + REPO: ${{ inputs.repo }} + run: | + ref="$(.github/workflows/cross-library/scripts/resolve-ref.sh "$REPO" "${{ matrix.ref_mode }}")" + echo "Resolved ${{ matrix.ref_mode }} ref for $REPO: $ref" + echo "ref=$ref" >> "$GITHUB_OUTPUT" + + # Compile-only (never `make check`). A compile failure is allowed ONLY if + # a wolfSSL commit since the last release tag declared it with a + # `breaks-=` token (see check-break.sh); otherwise the job + # fails, forcing intentional breaks to be recorded in a commit. + # PRODUCT_CONFIGURE is intentionally unquoted so multiple flags split. + - name: Compile ${{ inputs.product }} against wolfSSL + env: + REPO: ${{ inputs.repo }} + PRODUCT_CONFIGURE: ${{ inputs.product_configure }} + PRODUCT: ${{ inputs.product }} + REF: ${{ steps.ref.outputs.ref }} + MODE: ${{ matrix.ref_mode }} + run: | + S=.github/workflows/cross-library/scripts + set +e + # shellcheck disable=SC2086 # $PRODUCT_CONFIGURE must word-split into flags + "$S/${{ inputs.script }}" -t "$REF" \ + "$GITHUB_WORKSPACE/wolfssl-install" "$REPO" $PRODUCT_CONFIGURE + rc=$? + set -e + + # The breaks-= mechanism applies ONLY to the latest release + # tag. A head/master break is never waivable, never consults the + # ledger, and must be fixed. + if [ "$rc" -eq 0 ]; then + if [ "$MODE" = "latest" ] && "$S/check-break.sh" "$PRODUCT" "$REF" >/tmp/brk 2>/dev/null; then + echo "::warning::$PRODUCT ($REF) compiled OK but a break is still declared, remove the stale breaks-$PRODUCT=$REF token:" + cat /tmp/brk + fi + exit 0 + fi + + echo "===== $PRODUCT ($REF) failed to compile against this wolfSSL =====" + + if [ "$MODE" = "latest" ]; then + # Released, immutable tag: allowed only if the exact tag is declared. + if "$S/check-break.sh" "$PRODUCT" "$REF"; then + echo "::warning::$PRODUCT $REF failed to compile, but this break is DECLARED (see above). Treating as a known/tracked break." + exit 0 + fi + echo "::error::$PRODUCT $REF (latest release) no longer compiles against this wolfSSL and no break is declared." + echo "That tag is a released version and cannot be changed. If wolfSSL is intentionally dropping" + echo "compatibility with it, record the break so CI tracks it and this job passes. Add this exact" + echo "token to a commit message in this PR (the release must be named explicitly):" + echo " breaks-$PRODUCT=$REF" + echo "Otherwise, rework the change so $PRODUCT $REF still builds." + else + # Product master/HEAD: NEVER waivable, no breaks- declaration exists. + echo "::error::$PRODUCT $REF (default branch) no longer compiles against this wolfSSL." + echo "A master/HEAD break cannot be waived; there is no breaks- declaration for it." + echo "$PRODUCT $REF must stay compatible with wolfSSL master. Fix it by either:" + echo " * reworking this PR so $PRODUCT $REF builds against wolfSSL master again, or" + echo " * putting up a matching fix on $PRODUCT's $REF branch, then re-running this job." + fi + exit 1 diff --git a/.github/workflows/cross-library/README.md b/.github/workflows/cross-library/README.md new file mode 100644 index 0000000000..4dc553683f --- /dev/null +++ b/.github/workflows/cross-library/README.md @@ -0,0 +1,124 @@ +# Cross-library compile testing + +Compile tests the wolfSSL product family (wolfSSH, wolfCLU, wolfTPM, wolfMQTT, +wolfPKCS11, wolfProvider) against **this** wolfSSL, so a wolfSSL change that +would stop a downstream product from *compiling* is caught in CI. + +**Compile only. There is no runtime testing here** (the product scripts run +`make`, never `make check`). Each product is built twice: at the HEAD of its +default branch and at its latest tagged release. + +> Note: the workflow `.yml` files (the reusable engine `cross-library.yml` and +> the per-product `cross-.yml` callers) live in `.github/workflows/` +> itself, because GitHub only discovers workflows directly in that directory, +> not in subfolders. Everything else (these scripts) lives here. + +## Layout + +``` +.github/workflows/ + cross-library.yml # reusable engine (on: workflow_call) + cross-.yml # one thin caller per product (matrix: head, latest) + cross-library/ + README.md # this file + scripts/ # all the machinery +``` + +## How a run works (per product, per ref) + +The engine (`cross-library.yml`) runs one job in a clean container +(`ubuntu:24.04` by default; a caller may pass `debian:13`): + +1. **Install build tools** with `apt-get` (`+ apt_packages` from the caller). +2. **Checkout wolfSSL** (full history + tags, for the break check below). +3. **`build-wolfssl.sh`** builds this checkout's wolfSSL with the product's + required `wolfssl_configure` flags and installs it to a local dir. +4. **`resolve-ref.sh`** resolves the ref to build: the highest version tag + (`ref_mode: latest`) or the default branch (`ref_mode: head`). +5. **`.sh`** clones the product at that ref and compiles it against the + installed wolfSSL (`--with-wolfssl=`). +6. If the compile fails, **`check-break.sh`** decides whether it was a + *declared* break (allowed, tracked) or an *undeclared* one (job fails). See + below. + +## Scripts + +| Script | Role | +|---|---| +| `build-wolfssl.sh ` | Build and install wolfSSL from ``. Configure flags are one `eval`-ed string so a quoted `CFLAGS=`/`C_EXTRA_FLAGS=` group survives. | +| `common.sh` | Shared helpers: `resolve_repo_url`, `default_branch` (master/main auto-detect), `latest_tag`, and `cross_build_autotools` (clone, configure `--with-wolfssl` plus `-I`/`-L`/rpath/`PKG_CONFIG_PATH`, then `make`). | +| `resolve-ref.sh ` | Echo the ref for `head` (default branch) or `latest` (highest tag). | +| `latest-tag.sh ` | Poll the highest version tag (`git ls-remote --sort=-v:refname`, robust to mixed tag styles). | +| `check-break.sh ` | Break declaration check (see below). | +| `.sh [-t ] [product_configure...]` | Per-product build. Most just call `cross_build_autotools`; `wolfprovider.sh` also passes `--with-openssl`. | + +## Break declarations + +Testing a product's last *release* against wolfSSL HEAD can legitimately fail +when wolfSSL intentionally changes an API. This applies **only to the `latest` +(release-tag) leg**. To keep such a break honest and auditable, it must be +**declared in a wolfSSL commit message** with a token naming the **exact release +tag**: + +``` +breaks-= e.g. breaks-wolfssh=v1.5.0-stable +``` + +There is **no `latest`/`head`/`*` shorthand**. The broken release must be named +explicitly, so every newly-broken release needs its own fresh, reviewable +declaration (an old token simply stops matching once the tag moves on). + +**A `head` (master) break is never declarable.** If a product's default branch +stops compiling against wolfSSL master, there is no token to wave it through. +The PR must be reworked, or a fix put up on the product's master. A token whose +value is `head`, `master`, `main`, or `*` is ignored outright. + +`check-break.sh` scans wolfSSL commits for a matching token (case-insensitive) +over a window of the **last two wolfSSL release cycles**, from the +*second-newest* `v*-stable` release tag to HEAD. Two cycles (not one) so that +when wolfSSL cuts a new release, a break declared in the prior cycle keeps being +honored for one more cycle, giving the downstream product time to ship a fixed +release before PRs go red again for the same known issue. (Release tags are +picked by version order, not commit ancestry, so wolfSSL's many non-release tags +like `*-CHKIN` are ignored.) Then the engine: + +| Compile | Declared? | Result | +|---|---|---| +| passes | no | green | +| passes | yes | green, plus a warning to remove the now-stale token | +| fails | yes | green, plus a warning "known/tracked break" (shows the commit) | +| fails | no | red. Fix it, or add a `breaks-=` token | + +Because the token names the exact tag, it automatically stops matching once the +product releases a newer tag, forcing a fresh, explicit declaration if the new +release is still broken. + +The failure message depends on which leg broke: + +- **`latest` (a released tag)**: the tag is immutable, so the job explains the + `breaks-=` mechanism and asks you to either declare the break + (to track it and go green) or rework the change so the release still builds. +- **`head` (the product's default branch)**: this is expected to track wolfSSL + and is **never waivable**. The job fails and asks you to **rework the PR or put + up a fix on the product's master branch**. There is no break declaration for a + head failure. + +## Adding a product + +1. Copy an existing `cross-.yml` caller and set `product`, `repo`, + `wolfssl_configure` (the wolfSSL flags that product documents), optional + `product_configure`, `script`, and optional `apt_packages`. +2. Add a `scripts/.sh`. If it is a standard autotools project, it is + just: + ```sh + #!/usr/bin/env bash + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + . "$DIR/common.sh" + cross_build_autotools "$@" + ``` + Non-autotools products (see `wolfprovider.sh`) can `_prepare "$@"` and then + run their own build steps. + +Get each product's required `wolfssl_configure` from that product's own +README or CI, not by guessing. The flags matter (e.g. wolfPKCS11 and +wolfProvider need specific `C_EXTRA_FLAGS`/`CFLAGS` defines). diff --git a/.github/workflows/cross-library/scripts/build-wolfssl.sh b/.github/workflows/cross-library/scripts/build-wolfssl.sh new file mode 100755 index 0000000000..21c0828072 --- /dev/null +++ b/.github/workflows/cross-library/scripts/build-wolfssl.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Build the wolfSSL sources in with the given configure flags and +# install to . In CI is this checkout (the PR merge +# commit). The resulting is handed to a product build script so +# it can locate wolfSSL via --with-wolfssl=. +# +# Compile + install only; wolfSSL's own tests are not run here. +# +# The configure flags are one string (not separate args) and are `eval`-ed so a +# quoted CFLAGS/C_EXTRA_FLAGS group survives intact, e.g. +# --enable-all CFLAGS="-DWC_RSA_DIRECT -DHAVE_AES_ECB" +# The string comes from our own caller workflows (trusted input). +# +# Usage: build-wolfssl.sh + +set -euxo pipefail + +src="$1" +prefix="$2" +configure="${3:-}" + +cd "$src" +./autogen.sh +eval "./configure --prefix='$prefix' $configure" +make "-j$(nproc)" +make install diff --git a/.github/workflows/cross-library/scripts/check-break.sh b/.github/workflows/cross-library/scripts/check-break.sh new file mode 100755 index 0000000000..28ac1b9f0e --- /dev/null +++ b/.github/workflows/cross-library/scripts/check-break.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# +# Decide whether an intentional wolfSSL break of a downstream product has been +# DECLARED in a commit message, so the cross-library job can tell an accountable +# break from an accidental one. +# +# ONLY a break of a product's latest RELEASE TAG can be declared, i.e. a +# released, immutable version that wolfSSL is intentionally dropping +# compatibility with. The declaring commit names that exact tag: +# +# breaks-= e.g. breaks-wolfssh=v1.5.0-stable +# +# There is deliberately no shorthand (no latest/head/*), and there is NO way to +# declare a break of a product's master/HEAD: if wolfSSL master breaks a +# product's master, the PR must be reworked or the product's master fixed, and +# it is never waived. (The engine only consults this script for the latest-tag leg.) +# +# The scan window is "the release tag BEFORE the last one .. HEAD", i.e. the +# last TWO wolfSSL release cycles. Two (not one) so that when wolfSSL cuts a new +# release, a break declared in the prior cycle keeps being recognized for one +# more cycle, giving the downstream product time to ship a fixed release before +# PRs go red again for the known issue. Matching is case-insensitive. +# +# Exit 0 (prints the declaring commits) if the (product, ref) break is declared; +# exit 1 otherwise. Requires full history + tags in the checkout +# (actions/checkout fetch-depth: 0, fetch-tags: true). +# +# Usage: check-break.sh + +set -uo pipefail + +product="$1" +ref="$2" + +lc() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; } +ref_lc="$(lc "$ref")" + +# Fail LOUD, never fail open. This is a gate: if git cannot read the repository +# (e.g. a container-job "dubious ownership" refusal, or a shallow/absent +# checkout), every git call below returns nothing and the scan would silently +# report "no break declared", turning an environment failure into a wrong +# compatibility verdict. Refuse to run rather than answer from a broken repo. +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "check-break: ERROR: cannot read git history at '$(pwd)':" >&2 + git rev-parse --is-inside-work-tree 1>&2 || true # re-run to surface git's own message + echo "check-break: not a usable git checkout (dubious ownership in a container job, or missing history/tags)." >&2 + exit 2 +fi + +# Window: the release tag BEFORE the last one .. HEAD (last two release cycles, +# for the one-release grace period described above). We pick release tags by +# version order, not commit ancestry: wolfSSL carries many non-release tags +# (e.g. *-CHKIN, wolfEntropy*) that `git describe` would otherwise land on. +# RELEASE_GLOB matches wolfSSL's release tags (vX.Y.Z-stable). +RELEASE_GLOB='v*-stable' +rel="$(git tag --merged HEAD --sort=-v:refname --list "$RELEASE_GLOB" 2>/dev/null)" +# Line 2 = the release before the newest (our scan base). Fall back to the +# newest (line 1) if only one exists, or full history if there are no release +# tags (e.g. an unexpectedly shallow clone). +base="$(printf '%s\n' "$rel" | sed -n '2p')" +[ -n "$base" ] || base="$(printf '%s\n' "$rel" | sed -n '1p')" +range="HEAD" +[ -n "$base" ] && range="${base}..HEAD" + +# Diagnostics (to stderr, so they always show in the CI log). This is what makes +# a "no break is declared" failure debuggable: it prints the scan window and the +# actual commit messages/tokens check-break.sh is looking at. git stderr is NOT +# suppressed here (unlike the scan below) so a real git failure, e.g. "dubious +# ownership" of the workspace in a container job, surfaces instead of silently +# looking like "no token found". +{ + echo "check-break: product='${product}' ref='${ref}'" + echo "check-break: release tags merged into HEAD (newest first):" + printf '%s\n' "$rel" | sed 's/^/ /' + echo "check-break: scan base='${base:-}' range='${range}'" + echo "check-break: commit messages in range (hash subject):" + git log "$range" --no-merges --format=' %h %s' 2>&1 | sed -n '1,300p' + echo "check-break: breaks-${product}= tokens found in range:" + git log "$range" --no-merges --format='%B' 2>&1 \ + | grep -ioaE "breaks-${product}=[^[:space:]]+" | sed 's/^/ /' \ + || echo " (none)" +} >&2 + +declared=0 +while IFS= read -r val; do + [ -z "$val" ] && continue + v="$(lc "$val")" + # A break target may ONLY be an exact release tag. Branch names and + # wildcards (head, master, main, latest, all, *) are never valid break + # targets and are ignored outright, so master/HEAD breaks are not waivable. + case "$v" in + head|master|main|latest|all|*'*'*) continue ;; + esac + [ "$v" = "$ref_lc" ] && declared=1 +done < <(git log "$range" --no-merges --format='%B' 2>/dev/null \ + | grep -ioaE "breaks-${product}=[^[:space:]]+" \ + | sed 's/^[^=]*=//') + +if [ "$declared" -eq 1 ]; then + echo "Declared break: '${product}' at '${ref}' is covered by a breaks-${product}= token (since ${base:-}):" + git log "$range" --no-merges -i --grep="breaks-${product}=" \ + --format=' %h %s (%an)' 2>/dev/null || true + exit 0 +fi +exit 1 diff --git a/.github/workflows/cross-library/scripts/common.sh b/.github/workflows/cross-library/scripts/common.sh new file mode 100755 index 0000000000..9887f87abb --- /dev/null +++ b/.github/workflows/cross-library/scripts/common.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# +# Shared helpers for the wolfSSL cross-library compile checks. +# +# Each cross-library script builds a downstream wolfSSL product against a +# locally-built, already-installed wolfSSL and confirms it COMPILES. There is +# no runtime testing here (no `make check`): the goal is only to catch changes +# in wolfSSL that would stop a product from compiling. +# +# Product-script interface (see wolfssh.sh etc.): +# .sh [-t ] [product_configure...] +# -t build that git ref (tag or branch) +# (no -t) build HEAD of the repo's default branch (master or main, +# auto-detected, no guessing) + +set -euo pipefail + +# resolve_repo_url +# Accept either "owner/repo" shorthand or a full git URL / scp-style remote and +# echo a clonable URL. +resolve_repo_url() { + local repo="$1" + case "$repo" in + *://*|git@*) printf '%s\n' "$repo" ;; + *) printf 'https://github.com/%s.git\n' "$repo" ;; + esac +} + +# default_branch +# Echo the remote's default branch (e.g. master or main). Falls back to master. +default_branch() { + local url="$1" br + br="$(git ls-remote --symref "$url" HEAD 2>/dev/null \ + | sed -n 's@^ref:[[:space:]]*refs/heads/\([^[:space:]]*\)[[:space:]]*HEAD$@\1@p')" + printf '%s\n' "${br:-master}" +} + +# latest_tag +# Echo the highest version tag on the remote, or nothing if it has no tags. +# Uses git's version sort (--sort=-v:refname), which is robust to a repo mixing +# tag styles (e.g. wolfTPM has both "v4.0.0" and a legacy "v.1.8" that GNU +# `sort -V` mis-orders to the top). sed picks line 1 without cutting git's pipe, +# so `set -o pipefail` stays happy. +latest_tag() { + local url="$1" + git ls-remote --tags --refs --sort='-v:refname' "$url" \ + | sed -n '1s@.*refs/tags/@@p' +} + +# Globals populated by _prepare, consumed by the build functions below. +CL_INSTALL="" +CL_REPO="" +CL_SRC="" +CL_REF="" +CL_CONFIGURE=() + +# _prepare [-t ] [configure...] +# Parse args, resolve the ref (tag via -t, else default branch), shallow-clone +# the product, and leave CWD inside the cloned tree. +_prepare() { + local tag="" opt OPTIND=1 + while getopts ":t:" opt; do + case "$opt" in + t) tag="$OPTARG" ;; + *) echo "usage: $0 [-t ] [configure...]" >&2 + exit 2 ;; + esac + done + shift $((OPTIND - 1)) + + CL_INSTALL="$1"; shift + CL_REPO="$1"; shift + CL_CONFIGURE=("$@") + + local url + url="$(resolve_repo_url "$CL_REPO")" + CL_SRC="$(basename "$CL_REPO" .git)" + + if [ -n "$tag" ]; then + CL_REF="$tag" + else + CL_REF="$(default_branch "$url")" + fi + + echo "==> Building ${CL_REPO} @ ${CL_REF} against wolfSSL in ${CL_INSTALL}" + set -x + git clone --depth 1 --branch "$CL_REF" "$url" "$CL_SRC" + cd "$CL_SRC" + set +x +} + +# cross_build_autotools [-t ] [configure...] +# Standard autotools product: configure --with-wolfssl and compile (no check). +cross_build_autotools() { + _prepare "$@" + set -x + if [ -x ./autogen.sh ]; then + ./autogen.sh + fi + # Point the compiler/linker at the installed wolfSSL. --with-wolfssl alone + # is not enough for every product: some configure link-tests need -L to + # find -lwolfssl, and some example/app compiles need -I to find headers + # (e.g. wolfssl/options.h). rpath lets the built binaries run without + # LD_LIBRARY_PATH, and PKG_CONFIG_PATH covers products that probe pkg-config. + export PKG_CONFIG_PATH="${CL_INSTALL}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" + ./configure --with-wolfssl="$CL_INSTALL" "${CL_CONFIGURE[@]}" \ + CPPFLAGS="-I${CL_INSTALL}/include" \ + LDFLAGS="-L${CL_INSTALL}/lib -Wl,-rpath,${CL_INSTALL}/lib" + make "-j$(nproc)" + set +x +} diff --git a/.github/workflows/cross-library/scripts/latest-tag.sh b/.github/workflows/cross-library/scripts/latest-tag.sh new file mode 100755 index 0000000000..ddc9930a9d --- /dev/null +++ b/.github/workflows/cross-library/scripts/latest-tag.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Poll a product repo for its highest version tag (version-sorted) and echo it. +# Used by the workflow to resolve the "latest release" matrix leg, which is then +# passed to the product script as `-t `. +# +# Usage: latest-tag.sh (repo: "owner/repo" or a full git URL) +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" + +url="$(resolve_repo_url "$1")" +tag="$(latest_tag "$url")" +if [ -z "$tag" ]; then + echo "no tags found for $1" >&2 + exit 1 +fi +printf '%s\n' "$tag" diff --git a/.github/workflows/cross-library/scripts/resolve-ref.sh b/.github/workflows/cross-library/scripts/resolve-ref.sh new file mode 100755 index 0000000000..cfb6550448 --- /dev/null +++ b/.github/workflows/cross-library/scripts/resolve-ref.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Echo the git ref a product should be built at: +# mode=latest -> the highest version tag (polled) +# mode=head -> the default branch (master/main, auto-detected) +# Usage: resolve-ref.sh +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" + +repo="$1"; mode="$2" +url="$(resolve_repo_url "$repo")" + +case "$mode" in + latest) + t="$(latest_tag "$url")" + [ -n "$t" ] || { echo "no tags found for $repo" >&2; exit 1; } + printf '%s\n' "$t" ;; + head) + default_branch "$url" ;; + *) + echo "resolve-ref: unknown mode '$mode' (expected 'head' or 'latest')" >&2 + exit 2 ;; +esac diff --git a/.github/workflows/cross-library/scripts/wolfclu.sh b/.github/workflows/cross-library/scripts/wolfclu.sh new file mode 100755 index 0000000000..71bc096c2a --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolfclu.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Compile-check wolfclu against a locally-built wolfSSL. +# Usage: wolfclu.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" +cross_build_autotools "$@" diff --git a/.github/workflows/cross-library/scripts/wolfmqtt.sh b/.github/workflows/cross-library/scripts/wolfmqtt.sh new file mode 100755 index 0000000000..88d1d1ad09 --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolfmqtt.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Compile-check wolfmqtt against a locally-built wolfSSL. +# Usage: wolfmqtt.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" +cross_build_autotools "$@" diff --git a/.github/workflows/cross-library/scripts/wolfpkcs11.sh b/.github/workflows/cross-library/scripts/wolfpkcs11.sh new file mode 100755 index 0000000000..d6a52af654 --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolfpkcs11.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Compile-check wolfpkcs11 against a locally-built wolfSSL. +# Usage: wolfpkcs11.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" +cross_build_autotools "$@" diff --git a/.github/workflows/cross-library/scripts/wolfprovider.sh b/.github/workflows/cross-library/scripts/wolfprovider.sh new file mode 100755 index 0000000000..c9e587d0be --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolfprovider.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Compile-check wolfProvider against a locally-built wolfSSL. +# +# wolfProvider is an OpenSSL 3.x provider, so unlike the other products it also +# needs OpenSSL dev headers/libs (installed via the caller's apt_packages, e.g. +# libssl-dev) and an extra --with-openssl. NOTE: the exact flag set may need +# tuning per release; adjust the caller's wolfssl_configure / product_configure. +# +# Usage: wolfprovider.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" + +_prepare "$@" +set -x +if [ -x ./autogen.sh ]; then + ./autogen.sh +fi +export PKG_CONFIG_PATH="${CL_INSTALL}/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +./configure --with-wolfssl="$CL_INSTALL" --with-openssl=/usr "${CL_CONFIGURE[@]}" \ + CPPFLAGS="-I${CL_INSTALL}/include" \ + LDFLAGS="-L${CL_INSTALL}/lib -Wl,-rpath,${CL_INSTALL}/lib" +make "-j$(nproc)" +set +x diff --git a/.github/workflows/cross-library/scripts/wolfssh.sh b/.github/workflows/cross-library/scripts/wolfssh.sh new file mode 100755 index 0000000000..3ee86826c9 --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolfssh.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Compile-check wolfssh against a locally-built wolfSSL. +# Usage: wolfssh.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" +cross_build_autotools "$@" diff --git a/.github/workflows/cross-library/scripts/wolftpm.sh b/.github/workflows/cross-library/scripts/wolftpm.sh new file mode 100755 index 0000000000..521d73417b --- /dev/null +++ b/.github/workflows/cross-library/scripts/wolftpm.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Compile-check wolftpm against a locally-built wolfSSL. +# Usage: wolftpm.sh [-t ] [product_configure...] +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./common.sh +. "$DIR/common.sh" +cross_build_autotools "$@" diff --git a/.github/workflows/cross-wolfclu.yml b/.github/workflows/cross-wolfclu.yml new file mode 100644 index 0000000000..39aa09a5f2 --- /dev/null +++ b/.github/workflows/cross-wolfclu.yml @@ -0,0 +1,24 @@ +name: wolfCLU cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolfclu + repo: wolfSSL/wolfCLU + wolfssl_configure: --enable-wolfclu + product_configure: '' + script: wolfclu.sh diff --git a/.github/workflows/cross-wolfmqtt.yml b/.github/workflows/cross-wolfmqtt.yml new file mode 100644 index 0000000000..fcaf78475e --- /dev/null +++ b/.github/workflows/cross-wolfmqtt.yml @@ -0,0 +1,25 @@ +name: wolfMQTT cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolfmqtt + repo: wolfSSL/wolfMQTT + # wolfMQTT needs --enable-base64encode; --enable-all covers it. + wolfssl_configure: --enable-all + product_configure: '' + script: wolfmqtt.sh diff --git a/.github/workflows/cross-wolfpkcs11.yml b/.github/workflows/cross-wolfpkcs11.yml new file mode 100644 index 0000000000..c321bef90d --- /dev/null +++ b/.github/workflows/cross-wolfpkcs11.yml @@ -0,0 +1,28 @@ +name: wolfPKCS11 cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolfpkcs11 + repo: wolfSSL/wolfPKCS11 + # wolfPKCS11's documented wolfSSL build (see its README). + wolfssl_configure: >- + --enable-aescfb --enable-rsapss --enable-keygen --enable-pwdbased + --enable-scrypt + C_EXTRA_FLAGS="-DWOLFSSL_PUBLIC_MP -DWC_RSA_DIRECT -DHAVE_AES_ECB -DHAVE_AES_KEYWRAP" + product_configure: '' + script: wolfpkcs11.sh diff --git a/.github/workflows/cross-wolfprovider.yml b/.github/workflows/cross-wolfprovider.yml new file mode 100644 index 0000000000..ec686c2cbe --- /dev/null +++ b/.github/workflows/cross-wolfprovider.yml @@ -0,0 +1,29 @@ +name: wolfProvider cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolfprovider + repo: wolfSSL/wolfProvider + # wolfProvider's documented wolfSSL build (see its scripts/utils-wolfssl.sh). + wolfssl_configure: >- + --enable-all-crypto --with-eccminsz=192 --with-max-ecc-bits=1024 + --enable-opensslcoexist --enable-sha + CFLAGS="-DWC_RSA_NO_PADDING -DWOLFSSL_PUBLIC_MP -DHAVE_PUBLIC_FFDHE -DHAVE_FFDHE_6144 -DHAVE_FFDHE_8192 -DWOLFSSL_PSS_LONG_SALT -DWOLFSSL_PSS_SALT_LEN_DISCOVER -DRSA_MIN_SIZE=1024 -DWOLFSSL_OLD_OID_SUM" + product_configure: '' + script: wolfprovider.sh + apt_packages: libssl-dev diff --git a/.github/workflows/cross-wolfssh.yml b/.github/workflows/cross-wolfssh.yml new file mode 100644 index 0000000000..b08d42a6c7 --- /dev/null +++ b/.github/workflows/cross-wolfssh.yml @@ -0,0 +1,25 @@ +name: wolfSSH cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolfssh + repo: wolfSSL/wolfssh + # wolfSSH's own CI builds wolfSSL with --enable-all. + wolfssl_configure: --enable-all + product_configure: --enable-all + script: wolfssh.sh diff --git a/.github/workflows/cross-wolftpm.yml b/.github/workflows/cross-wolftpm.yml new file mode 100644 index 0000000000..ac946369bd --- /dev/null +++ b/.github/workflows/cross-wolftpm.yml @@ -0,0 +1,24 @@ +name: wolfTPM cross-library + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + cross-library: + uses: ./.github/workflows/cross-library.yml + with: + product: wolftpm + repo: wolfSSL/wolfTPM + wolfssl_configure: --enable-wolftpm --enable-pkcallbacks --enable-keygen + product_configure: '' + script: wolftpm.sh