From b99d0981ed5b2233583769fe8457310b6e15cd63 Mon Sep 17 00:00:00 2001 From: Yuansheng Wang Date: Sun, 31 May 2026 14:31:44 +0800 Subject: [PATCH] ci: reduce redundant workflow work --- .github/actions/setup-lua/action.yml | 42 ++ .github/scripts/install-lua-test-deps.sh | 10 + .github/scripts/latest-rockspec.py | 69 +++ .github/scripts/resolve-luajit.sh | 22 + .github/scripts/run-openresty-runtime.sh | 39 +- .github/workflows/changelog-policy.yml | 4 + .github/workflows/ci.yml | 403 ++++++++---------- .github/workflows/fuzz.yml | 69 +++ .../workflows/lua-lazy-mutation-property.yml | 30 +- .github/workflows/release.yml | 31 +- .github/workflows/supply-chain-audit.yml | 28 ++ 11 files changed, 448 insertions(+), 299 deletions(-) create mode 100644 .github/actions/setup-lua/action.yml create mode 100755 .github/scripts/install-lua-test-deps.sh create mode 100755 .github/scripts/latest-rockspec.py create mode 100755 .github/scripts/resolve-luajit.sh create mode 100644 .github/workflows/fuzz.yml create mode 100644 .github/workflows/supply-chain-audit.yml diff --git a/.github/actions/setup-lua/action.yml b/.github/actions/setup-lua/action.yml new file mode 100644 index 0000000..a285cfb --- /dev/null +++ b/.github/actions/setup-lua/action.yml @@ -0,0 +1,42 @@ +name: Setup Lua +description: Install a Lua runtime, LuaRocks, and optional LuaRocks packages. + +inputs: + lua-version: + description: Runtime version accepted by leafo/gh-actions-lua. + required: true + luarocks-version: + description: LuaRocks version to install. + required: false + default: "3.11.1" + install-luarocks: + description: Install LuaRocks with leafo/gh-actions-luarocks. + required: false + default: "true" + packages: + description: Whitespace-separated LuaRocks packages to install. + required: false + default: "" + +runs: + using: composite + steps: + - name: Install Lua + uses: leafo/gh-actions-lua@v13 + with: + luaVersion: ${{ inputs.lua-version }} + + - name: Install LuaRocks + if: inputs.install-luarocks == 'true' + uses: leafo/gh-actions-luarocks@v4 + with: + luarocksVersion: ${{ inputs.luarocks-version }} + + - name: Install LuaRocks packages + if: inputs.install-luarocks == 'true' && inputs.packages != '' + shell: bash + run: | + set -euo pipefail + for package in ${{ inputs.packages }}; do + luarocks install "$package" + done diff --git a/.github/scripts/install-lua-test-deps.sh b/.github/scripts/install-lua-test-deps.sh new file mode 100755 index 0000000..5aeb607 --- /dev/null +++ b/.github/scripts/install-lua-test-deps.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +sudo apt-get update +sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks + +# Ubuntu LuaRocks targets Lua 5.1 by default; LuaJIT is ABI-compatible +# with 5.1 so rocks built for 5.1 load fine under each runtime. +sudo /usr/bin/luarocks install busted +sudo /usr/bin/luarocks install lua-cjson diff --git a/.github/scripts/latest-rockspec.py b/.github/scripts/latest-rockspec.py new file mode 100755 index 0000000..6974a65 --- /dev/null +++ b/.github/scripts/latest-rockspec.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import argparse +import re +from pathlib import Path + + +def prerelease_key(value): + if value is None: + return () + key = [] + for part in value.split("."): + if part.isdigit(): + key.append((0, int(part))) + else: + key.append((1, part)) + return tuple(key) + + +def latest_any(): + pattern = re.compile( + r"^lua-qjson-(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?-(\d+)\.rockspec$" + ) + matches = [] + for path in Path("rockspec").glob("lua-qjson-*.rockspec"): + match = pattern.match(path.name) + if match: + major, minor, patch, prerelease, revision = match.groups() + matches.append( + ( + int(major), + int(minor), + int(patch), + prerelease is None, + prerelease_key(prerelease), + int(revision), + str(path), + ) + ) + if not matches: + raise SystemExit("no lua-qjson rockspec found") + return max(matches)[-1] + + +def latest_for_version(version): + version = version.removeprefix("v") + pattern = re.compile(r"^lua-qjson-" + re.escape(version) + r"-(\d+)\.rockspec$") + matches = [] + for path in Path("rockspec").glob("lua-qjson-" + version + "-*.rockspec"): + match = pattern.match(path.name) + if match: + matches.append((int(match.group(1)), str(path))) + if not matches: + raise SystemExit("rockspec file not found for version " + version) + return max(matches)[1] + + +def main(): + parser = argparse.ArgumentParser(description="Print the newest lua-qjson rockspec path.") + parser.add_argument("--version", help="Select the newest revision for a release version.") + args = parser.parse_args() + + if args.version: + print(latest_for_version(args.version)) + else: + print(latest_any()) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/resolve-luajit.sh b/.github/scripts/resolve-luajit.sh new file mode 100755 index 0000000..2e7c6ac --- /dev/null +++ b/.github/scripts/resolve-luajit.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -eq 0 ]; then + set -- luajit luajit-2.1.0-beta3 lua +fi + +for candidate in "$@"; do + if command -v "$candidate" >/dev/null 2>&1; then + lua_bin="$(command -v "$candidate")" + if "$lua_bin" -e 'assert(jit, "LuaJIT required")' >/dev/null 2>&1; then + if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "path=$lua_bin" >> "$GITHUB_OUTPUT" + fi + "$lua_bin" -e 'print(jit.version)' + exit 0 + fi + fi +done + +echo "LuaJIT executable not found" >&2 +exit 1 diff --git a/.github/scripts/run-openresty-runtime.sh b/.github/scripts/run-openresty-runtime.sh index 83ae71e..3faa645 100755 --- a/.github/scripts/run-openresty-runtime.sh +++ b/.github/scripts/run-openresty-runtime.sh @@ -5,6 +5,7 @@ set -euo pipefail workspace="${GITHUB_WORKSPACE:-$(pwd)}" libqjson="$workspace/target/release/libqjson.so" +full_busted="${OPENRESTY_FULL_BUSTED:-false}" if [[ ! -f "$libqjson" ]]; then echo "missing release cdylib: $libqjson" >&2 @@ -13,6 +14,7 @@ fi docker run --rm \ -e DEBIAN_FRONTEND=noninteractive \ + -e OPENRESTY_FULL_BUSTED="$full_busted" \ -v "$workspace:/workspace" \ -w /workspace \ "$OPENRESTY_IMAGE" \ @@ -22,31 +24,34 @@ set -euo pipefail OPENRESTY=/usr/local/openresty RESTY="$OPENRESTY/bin/resty" LUAJIT="$OPENRESTY/luajit/bin/luajit" -BUSTED="$OPENRESTY/luajit/bin/busted" test -x "$RESTY" test -x "$LUAJIT" -apt-get update -apt-get install -y --no-install-recommends \ - ca-certificates \ - liblua5.1-0-dev \ - lua5.1 \ - luarocks - -# Use Ubuntu LuaRocks here. The OpenResty-bundled luarocks command runs under -# LuaJIT and can fail to load the current public manifest due to bytecode limits. -/usr/bin/luarocks install busted -/usr/bin/luarocks install lua-cjson - -test -x "$BUSTED" -"$RESTY" -V >/dev/null 2>&1 +"$RESTY" -V "$LUAJIT" -e '\''assert(jit, "LuaJIT required"); print(jit.version)'\'' export LD_LIBRARY_PATH=/workspace/target/release export LUA_PATH="/workspace/lua/?.lua;;" "$RESTY" /workspace/.github/scripts/openresty-smoke.lua -"$BUSTED" --lua="$LUAJIT" /workspace/tests/lua \ - --lpath="/workspace/lua/?.lua" + +if [ "${OPENRESTY_FULL_BUSTED:-false}" = "true" ]; then + apt-get update + apt-get install -y --no-install-recommends \ + ca-certificates \ + liblua5.1-0-dev \ + lua5.1 \ + luarocks + + # Use Ubuntu LuaRocks here. The OpenResty-bundled luarocks command runs under + # LuaJIT and can fail to load the current public manifest due to bytecode limits. + /usr/bin/luarocks install busted + /usr/bin/luarocks install lua-cjson + + BUSTED="$(command -v busted)" + test -x "$BUSTED" + "$BUSTED" --lua="$LUAJIT" /workspace/tests/lua \ + --lpath="/workspace/lua/?.lua" +fi ' diff --git a/.github/workflows/changelog-policy.yml b/.github/workflows/changelog-policy.yml index 2374994..eb94887 100644 --- a/.github/workflows/changelog-policy.yml +++ b/.github/workflows/changelog-policy.yml @@ -9,6 +9,10 @@ on: - "CHANGELOG.md" - ".github/workflows/changelog-policy.yml" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d43af7c..24743dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,13 @@ on: branches: [master, main] pull_request: +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always @@ -14,16 +21,98 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check dependency file changes + id: audit_changes + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + CURRENT_SHA: ${{ github.sha }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" = "pull_request" ]; then + base="$PR_BASE_SHA" + head="$CURRENT_SHA" + range="$base...$head" + else + base="$PUSH_BEFORE_SHA" + head="$CURRENT_SHA" + range="$base..$head" + fi + + if [ -z "$base" ] || [[ "$base" =~ ^0+$ ]]; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "No usable base SHA; running cargo audit." + exit 0 + fi + + if ! changed_paths="$(git diff --name-only "$range")"; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "Could not diff $range; running cargo audit." + exit 0 + fi + + if grep -Eq '^(Cargo\.toml|Cargo\.lock)$' <<<"$changed_paths"; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "Dependency files changed; running cargo audit." + else + echo "run=false" >> "$GITHUB_OUTPUT" + echo "Cargo.toml/Cargo.lock unchanged; skipping cargo audit." + fi + + - name: Install cargo-audit + if: steps.audit_changes.outputs.run == 'true' + uses: taiki-e/install-action@v2 + with: + tool: cargo-audit + + - name: Security audit + if: steps.audit_changes.outputs.run == 'true' + run: cargo audit + + - name: Report skipped audit + if: steps.audit_changes.outputs.run != 'true' + run: echo "cargo audit skipped because dependency manifests did not change" + + build: + name: Build release cdylib (linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust (stable) run: | rustup toolchain install stable --profile minimal --no-self-update rustup default stable - - name: Security audit - run: | - cargo install cargo-audit --locked - cargo audit + - name: Cache cargo registry & target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: build-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml', 'Cargo.lock') }} + restore-keys: | + build-${{ runner.os }}-${{ runner.arch }}- + + - name: Build cdylib + run: cargo build --release + + - name: Upload release cdylib + uses: actions/upload-artifact@v4 + with: + name: qjson-linux-release + path: target/release/libqjson.so + if-no-files-found: error + retention-days: 1 rust: name: Rust tests (${{ matrix.os }}) @@ -48,12 +137,9 @@ jobs: ~/.cargo/registry ~/.cargo/git target - key: cargo-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml') }} + key: cargo-test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml', 'Cargo.lock') }} restore-keys: | - cargo-${{ runner.os }}-${{ runner.arch }}- - - - name: Build (release) - run: cargo build --release + cargo-test-${{ runner.os }}-${{ runner.arch }}- - name: Test (release) run: cargo test --release @@ -62,38 +148,57 @@ jobs: run: cargo test --release --no-default-features - name: Test debug scalar-only + if: matrix.os == 'ubuntu-latest' run: cargo test --no-default-features - name: Test with test-panic feature + if: matrix.os == 'ubuntu-latest' run: cargo test --features test-panic --release + - name: Install LuaJIT for package validation + if: matrix.os == 'macos-14' + uses: ./.github/actions/setup-lua + with: + lua-version: "luajit-2.1.0-beta3" + + - name: Resolve LuaJIT executable for package validation + if: matrix.os == 'macos-14' + id: mac_luajit + run: .github/scripts/resolve-luajit.sh + + - name: Validate LuaRocks package (macOS) + if: matrix.os == 'macos-14' + env: + LUAJIT_BIN: ${{ steps.mac_luajit.outputs.path }} + run: | + rm -rf /tmp/lua-qjson-rock + ROCKSPEC="$(.github/scripts/latest-rockspec.py)" + luarocks make "$ROCKSPEC" --tree /tmp/lua-qjson-rock + eval "$(luarocks path --tree /tmp/lua-qjson-rock)" + unset LD_LIBRARY_PATH + unset DYLD_LIBRARY_PATH + "$LUAJIT_BIN" -e 'local qjson = require("qjson"); local doc = qjson.parse("{\"a\":42}"); assert(doc:get_i64("a") == 42)' + lua-lint: name: Lua lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install LuaJIT - uses: leafo/gh-actions-lua@v13 + - name: Install Lua lint toolchain + uses: ./.github/actions/setup-lua with: - luaVersion: "luajit-2.1.0-beta3" - - - name: Install LuaRocks - uses: leafo/gh-actions-luarocks@v4 - with: - luarocksVersion: "3.11.1" - - - name: Install luacheck - run: | - luarocks install https://luarocks.org/argparse-0.7.2-1.src.rock - luarocks install https://luarocks.org/luafilesystem-1.9.0-1.src.rock - luarocks install https://luarocks.org/luacheck-1.2.0-1.src.rock + lua-version: "luajit-2.1.0-beta3" + packages: >- + https://luarocks.org/argparse-0.7.2-1.src.rock + https://luarocks.org/luafilesystem-1.9.0-1.src.rock + https://luarocks.org/luacheck-1.2.0-1.src.rock - name: Run Lua lint run: make lua-lint fuzz: - name: Fuzz regression guard + name: Fuzz corpus replay runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -112,7 +217,7 @@ jobs: ~/.cargo/git ~/.cargo/bin/cargo-fuzz fuzz/target - key: fuzz-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml', 'fuzz/Cargo.toml') }} + key: fuzz-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'fuzz/Cargo.toml') }} restore-keys: | fuzz-${{ runner.os }}-${{ runner.arch }}- @@ -122,14 +227,12 @@ jobs: cargo install --locked cargo-fuzz fi - - name: Run fuzz targets + - name: Replay fuzz corpora run: | - cargo +nightly fuzz run fuzz_parse_eager -- -max_total_time=60 - cargo +nightly fuzz run fuzz_depth -- -max_total_time=60 - cargo +nightly fuzz run fuzz_ffi_ops -- -max_total_time=60 - - - name: Run lazy parse fuzz target - run: cargo +nightly fuzz run fuzz_parse_lazy -- -max_total_time=60 + cargo +nightly fuzz run fuzz_parse_eager -- -runs=0 + cargo +nightly fuzz run fuzz_depth -- -runs=0 + cargo +nightly fuzz run fuzz_ffi_ops -- -runs=0 + cargo +nightly fuzz run fuzz_parse_lazy -- -runs=0 sanitizers: name: Sanitizers (Rust nightly) @@ -175,7 +278,7 @@ jobs: lua: name: Lua integration tests (${{ matrix.runtime.name }}) runs-on: ubuntu-latest - needs: rust + needs: build strategy: fail-fast: false matrix: @@ -183,66 +286,35 @@ jobs: - name: upstream LuaJIT lua_version: "luajit-2.1.0-beta3" validate_rockspec: true - - name: OpenResty LuaJIT - lua_version: "luajit-openresty" - validate_rockspec: false steps: - uses: actions/checkout@v4 with: submodules: recursive - - name: Install Rust (stable) - run: | - rustup toolchain install stable --profile minimal --no-self-update - rustup default stable - - - name: Cache cargo registry & target - uses: actions/cache@v4 + - name: Download release cdylib + uses: actions/download-artifact@v4 with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.toml') }} - restore-keys: | - cargo-${{ runner.os }}- + name: qjson-linux-release + path: target/release - name: Install LuaJIT (${{ matrix.runtime.name }}) - uses: leafo/gh-actions-lua@v13 + uses: ./.github/actions/setup-lua with: - luaVersion: ${{ matrix.runtime.lua_version }} - - - name: Install LuaRocks - run: | - sudo apt-get update - sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks - - - name: Build cdylib - run: cargo build --release + lua-version: ${{ matrix.runtime.lua_version }} + install-luarocks: "false" - name: Install Lua dependencies + run: .github/scripts/install-lua-test-deps.sh + + - name: Install Rust for package validation + if: matrix.runtime.validate_rockspec run: | - # Ubuntu LuaRocks targets Lua 5.1 by default; LuaJIT is ABI-compatible - # with 5.1 so rocks built for 5.1 load fine under each matrix runtime. - sudo luarocks install busted - sudo luarocks install lua-cjson + rustup toolchain install stable --profile minimal --no-self-update + rustup default stable - name: Resolve LuaJIT executable id: luajit - run: | - set -euo pipefail - for candidate in luajit luajit-2.1.0-beta3 lua; do - if command -v "$candidate" >/dev/null 2>&1; then - lua_bin="$(command -v "$candidate")" - if "$lua_bin" -e 'assert(jit, "LuaJIT required")' >/dev/null 2>&1; then - echo "path=$lua_bin" >> "$GITHUB_OUTPUT" - "$lua_bin" -e 'print(jit.version)' - exit 0 - fi - fi - done - echo "LuaJIT executable not found" >&2 - exit 1 + run: .github/scripts/resolve-luajit.sh - name: Run busted tests (${{ matrix.runtime.name }}) env: @@ -260,33 +332,7 @@ jobs: LUAJIT_BIN: ${{ steps.luajit.outputs.path }} run: | rm -rf /tmp/lua-qjson-rock - ROCKSPEC="$(python3 - <<'PY' - import re - from pathlib import Path - - pattern = re.compile(r"^lua-qjson-(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?-(\d+)\.rockspec$") - def prerelease_key(value): - if value is None: - return () - key = [] - for part in value.split("."): - if part.isdigit(): - key.append((0, int(part))) - else: - key.append((1, part)) - return tuple(key) - - matches = [] - for path in Path("rockspec").glob("lua-qjson-*.rockspec"): - match = pattern.match(path.name) - if match: - major, minor, patch, prerelease, revision = match.groups() - matches.append((int(major), int(minor), int(patch), prerelease is None, prerelease_key(prerelease), int(revision), str(path))) - if not matches: - raise SystemExit("no lua-qjson rockspec found") - print(max(matches)[-1]) - PY - )" + ROCKSPEC="$(.github/scripts/latest-rockspec.py)" luarocks make "$ROCKSPEC" --tree /tmp/lua-qjson-rock eval "$(luarocks path --tree /tmp/lua-qjson-rock)" unset LD_LIBRARY_PATH @@ -296,63 +342,33 @@ jobs: lua-valgrind: name: Lua valgrind memcheck (upstream LuaJIT) runs-on: ubuntu-latest + needs: build steps: - uses: actions/checkout@v4 with: submodules: recursive - - name: Install Rust (stable) - run: | - rustup toolchain install stable --profile minimal --no-self-update - rustup default stable - - - name: Cache cargo registry & target - uses: actions/cache@v4 + - name: Download release cdylib + uses: actions/download-artifact@v4 with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: valgrind-${{ runner.os }}-${{ hashFiles('Cargo.toml') }} - restore-keys: | - valgrind-${{ runner.os }}- + name: qjson-linux-release + path: target/release - name: Install LuaJIT - uses: leafo/gh-actions-lua@v13 + uses: ./.github/actions/setup-lua with: - luaVersion: "luajit-2.1.0-beta3" - - - name: Install LuaRocks and valgrind - run: | - sudo apt-get update - sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks valgrind - - - name: Build cdylib - run: cargo build --release + lua-version: "luajit-2.1.0-beta3" + install-luarocks: "false" - name: Install Lua dependencies - run: | - # Ubuntu LuaRocks targets Lua 5.1 by default; LuaJIT is ABI-compatible - # with 5.1 so rocks built for 5.1 load fine under upstream LuaJIT. - sudo luarocks install busted - sudo luarocks install lua-cjson + run: .github/scripts/install-lua-test-deps.sh + + - name: Install valgrind + run: sudo apt-get install -y valgrind - name: Resolve LuaJIT executable id: luajit - run: | - set -euo pipefail - for candidate in luajit luajit-2.1.0-beta3 lua; do - if command -v "$candidate" >/dev/null 2>&1; then - lua_bin="$(command -v "$candidate")" - if "$lua_bin" -e 'assert(jit, "LuaJIT required")' >/dev/null 2>&1; then - echo "path=$lua_bin" >> "$GITHUB_OUTPUT" - "$lua_bin" -e 'print(jit.version)' - exit 0 - fi - fi - done - echo "LuaJIT executable not found" >&2 - exit 1 + run: .github/scripts/resolve-luajit.sh - name: Run busted tests under valgrind (upstream LuaJIT) env: @@ -379,7 +395,7 @@ jobs: openresty: name: OpenResty runtime (${{ matrix.openresty.version }}) runs-on: ubuntu-22.04 - needs: rust + needs: build permissions: contents: read strategy: @@ -388,99 +404,26 @@ jobs: openresty: - version: "1.21.4.4" image: "openresty/openresty:1.21.4.4-0-jammy" + full_busted: false - version: "1.27.1.2" image: "openresty/openresty:1.27.1.2-0-jammy" + full_busted: false - version: "1.29.2.4" image: "openresty/openresty:1.29.2.4-0-jammy" + full_busted: true steps: - uses: actions/checkout@v4 with: submodules: recursive - - name: Install Rust (stable) - run: | - rustup toolchain install stable --profile minimal --no-self-update - rustup default stable - - - name: Cache cargo registry & target - uses: actions/cache@v4 + - name: Download release cdylib + uses: actions/download-artifact@v4 with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: openresty-${{ runner.os }}-${{ matrix.openresty.version }}-${{ hashFiles('Cargo.toml') }} - restore-keys: | - openresty-${{ runner.os }}-${{ matrix.openresty.version }}- - openresty-${{ runner.os }}- - - - name: Build cdylib - run: cargo build --release + name: qjson-linux-release + path: target/release - name: Run OpenResty runtime checks env: OPENRESTY_IMAGE: ${{ matrix.openresty.image }} + OPENRESTY_FULL_BUSTED: ${{ matrix.openresty.full_busted }} run: .github/scripts/run-openresty-runtime.sh - - package: - name: LuaRocks package (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - needs: rust - strategy: - matrix: - os: [ubuntu-latest, macos-14] - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Rust (stable) - run: | - rustup toolchain install stable --profile minimal --no-self-update - rustup default stable - - - name: Install LuaJIT - uses: leafo/gh-actions-lua@v13 - with: - luaVersion: "luajit-2.1.0-beta3" - - - name: Install LuaRocks - uses: leafo/gh-actions-luarocks@v4 - with: - luarocksVersion: "3.11.1" - - - name: Validate LuaRocks package - run: | - rm -rf /tmp/lua-qjson-rock - ROCKSPEC="$(python3 - <<'PY' - import re - from pathlib import Path - - pattern = re.compile(r"^lua-qjson-(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?-(\d+)\.rockspec$") - def prerelease_key(value): - if value is None: - return () - key = [] - for part in value.split("."): - if part.isdigit(): - key.append((0, int(part))) - else: - key.append((1, part)) - return tuple(key) - - matches = [] - for path in Path("rockspec").glob("lua-qjson-*.rockspec"): - match = pattern.match(path.name) - if match: - major, minor, patch, prerelease, revision = match.groups() - matches.append((int(major), int(minor), int(patch), prerelease is None, prerelease_key(prerelease), int(revision), str(path))) - if not matches: - raise SystemExit("no lua-qjson rockspec found") - print(max(matches)[-1]) - PY - )" - luarocks make "$ROCKSPEC" --tree /tmp/lua-qjson-rock - eval "$(luarocks path --tree /tmp/lua-qjson-rock)" - unset LD_LIBRARY_PATH - unset DYLD_LIBRARY_PATH - lua -e 'assert(jit, "LuaJIT required"); local qjson = require("qjson"); local doc = qjson.parse("{\"a\":42}"); assert(doc:get_i64("a") == 42)' diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..03b4490 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,69 @@ +name: Timed Fuzzing + +on: + schedule: + - cron: "23 4 * * 0" + workflow_dispatch: + inputs: + max_total_time: + description: "Seconds per fuzz target (default: 60)" + required: false + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + fuzz: + name: Timed fuzzing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Rust (nightly) + run: rustup toolchain install nightly --profile minimal --no-self-update + + - name: Cache cargo registry & fuzz target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + ~/.cargo/bin/cargo-fuzz + fuzz/target + key: timed-fuzz-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.toml', 'Cargo.lock', 'fuzz/Cargo.toml') }} + restore-keys: | + timed-fuzz-${{ runner.os }}-${{ runner.arch }}- + + - name: Install cargo-fuzz + run: | + if ! command -v cargo-fuzz >/dev/null 2>&1; then + cargo install --locked cargo-fuzz + fi + + - name: Run timed fuzz targets + env: + DISPATCH_MAX_TOTAL_TIME: ${{ github.event.inputs.max_total_time }} + run: | + set -euo pipefail + max_total_time="${DISPATCH_MAX_TOTAL_TIME:-}" + if [ -z "$max_total_time" ]; then + max_total_time=60 + fi + if ! [[ "$max_total_time" =~ ^[1-9][0-9]*$ ]]; then + echo "max_total_time must be a positive integer, got '$max_total_time'" >&2 + exit 1 + fi + + cargo +nightly fuzz run fuzz_parse_eager -- -max_total_time="$max_total_time" + cargo +nightly fuzz run fuzz_depth -- -max_total_time="$max_total_time" + cargo +nightly fuzz run fuzz_ffi_ops -- -max_total_time="$max_total_time" + cargo +nightly fuzz run fuzz_parse_lazy -- -max_total_time="$max_total_time" diff --git a/.github/workflows/lua-lazy-mutation-property.yml b/.github/workflows/lua-lazy-mutation-property.yml index 90bb58e..3ce9660 100644 --- a/.github/workflows/lua-lazy-mutation-property.yml +++ b/.github/workflows/lua-lazy-mutation-property.yml @@ -47,40 +47,20 @@ jobs: mut-prop-${{ runner.os }}- - name: Install LuaJIT - uses: leafo/gh-actions-lua@v13 + uses: ./.github/actions/setup-lua with: - luaVersion: "luajit-2.1.0-beta3" - - - name: Install LuaRocks - run: | - sudo apt-get update - sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks + lua-version: "luajit-2.1.0-beta3" + install-luarocks: "false" - name: Build cdylib (release) run: cargo build --release - name: Install Lua dependencies - run: | - # Ubuntu LuaRocks targets Lua 5.1 by default; LuaJIT is ABI-compatible. - sudo luarocks install busted - sudo luarocks install lua-cjson + run: .github/scripts/install-lua-test-deps.sh - name: Resolve LuaJIT executable id: luajit - run: | - set -euo pipefail - for candidate in luajit luajit-2.1.0-beta3 lua; do - if command -v "$candidate" >/dev/null 2>&1; then - lua_bin="$(command -v "$candidate")" - if "$lua_bin" -e 'assert(jit, "LuaJIT required")' >/dev/null 2>&1; then - echo "path=$lua_bin" >> "$GITHUB_OUTPUT" - "$lua_bin" -e 'print(jit.version)' - exit 0 - fi - fi - done - echo "LuaJIT executable not found" >&2 - exit 1 + run: .github/scripts/resolve-luajit.sh - name: Resolve stress parameters id: params diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32e6673..a26d7b3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,17 +26,10 @@ jobs: persist-credentials: false - name: Install Lua - uses: leafo/gh-actions-lua@v10 + uses: ./.github/actions/setup-lua with: - luaVersion: "5.1.5" - - - name: Install LuaRocks - uses: leafo/gh-actions-luarocks@v4 - with: - luarocksVersion: "3.11.1" - - - name: Install upload dependency - run: luarocks install dkjson + lua-version: "5.1.5" + packages: https://luarocks.org/dkjson-2.10-1.src.rock - name: Extract release name id: release_env @@ -81,23 +74,7 @@ jobs: env: VERSION_WITHOUT_V: ${{ steps.release_env.outputs.version_without_v }} run: | - rockspec_file="$(python3 - <<'PY' - import os - import re - from pathlib import Path - - version = os.environ["VERSION_WITHOUT_V"] - pattern = re.compile(r"^lua-qjson-" + re.escape(version) + r"-(\d+)\.rockspec$") - matches = [] - for path in Path("rockspec").glob("lua-qjson-" + version + "-*.rockspec"): - match = pattern.match(path.name) - if match: - matches.append((int(match.group(1)), str(path))) - if not matches: - raise SystemExit("rockspec file not found for version " + version) - print(max(matches)[1]) - PY - )" + rockspec_file="$(.github/scripts/latest-rockspec.py --version "$VERSION_WITHOUT_V")" echo "rockspec_file=${rockspec_file}" >> $GITHUB_OUTPUT - name: Extract changelog entry diff --git a/.github/workflows/supply-chain-audit.yml b/.github/workflows/supply-chain-audit.yml new file mode 100644 index 0000000..fa93f9d --- /dev/null +++ b/.github/workflows/supply-chain-audit.yml @@ -0,0 +1,28 @@ +name: Scheduled Supply-chain Audit + +on: + schedule: + - cron: "41 4 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + audit: + name: Supply-chain audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install cargo-audit + uses: taiki-e/install-action@v2 + with: + tool: cargo-audit + + - name: Security audit + run: cargo audit