Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/scripts/pr_touches_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Exit 0 if any changed file matches the calling workflow's on.push.paths
globs (honoring ! negations), else exit 1.

The `gate` job uses this to restore the path filtering that `pull_request`
supported but `pull_request_review` does not, so an approval only launches
CI for workflows whose relevant files actually changed.
"""
import argparse
import re
import sys

import yaml


def glob_to_regex(pat):
# GitHub Actions path glob -> regex. `**` spans directories, `*` does not
# cross `/`, `?` matches a single non-`/` char.
i, n, out = 0, len(pat), ["^"]
while i < n:
c = pat[i]
if c == "*":
if pat[i + 1 : i + 2] == "*":
out.append(".*")
i += 2
if pat[i : i + 1] == "/":
i += 1 # `**/` also matches zero directories
else:
out.append("[^/]*")
i += 1
elif c == "?":
out.append("[^/]")
i += 1
else:
out.append(re.escape(c))
i += 1
out.append("$")
return "".join(out)


def workflow_path(ref):
# "owner/repo/.github/workflows/x.yml@refs/heads/main" -> ".github/workflows/x.yml"
path = ref.split("@", 1)[0]
parts = path.split("/", 2)
return parts[2] if len(parts) == 3 else path


def read_patterns(paths_file, workflow_ref):
# An explicit paths file wins: it lets a caller gate on a narrower set than
# its own push.paths (e.g. run only when the pipeline definition changed).
if paths_file:
with open(paths_file) as f:
return [ln.strip() for ln in f if ln.strip()]
with open(workflow_path(workflow_ref)) as f:
spec = yaml.safe_load(f)
# YAML 1.1 parses the bare `on:` key as the boolean True.
on = spec.get("on", spec.get(True, {})) or {}
push = on.get("push") or {}
return push.get("paths")


def main():
ap = argparse.ArgumentParser()
ap.add_argument("--workflow-ref")
ap.add_argument("--paths-file")
ap.add_argument("--changed-files", required=True)
args = ap.parse_args()

if not args.paths_file and not args.workflow_ref:
ap.error("one of --paths-file or --workflow-ref is required")

patterns = read_patterns(args.paths_file, args.workflow_ref)
if not patterns:
return 0 # no path filter -> always relevant

pos = [re.compile(glob_to_regex(p)) for p in patterns if not p.startswith("!")]
neg = [re.compile(glob_to_regex(p[1:])) for p in patterns if p.startswith("!")]

with open(args.changed_files) as f:
files = [ln.strip() for ln in f if ln.strip()]

for path in files:
if any(r.match(path) for r in pos) and not any(r.match(path) for r in neg):
return 0
return 1


if __name__ == "__main__":
sys.exit(main())
89 changes: 67 additions & 22 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,84 @@
name: "CodeQL"

on:
# run on every push to the feature-development branch
# the main branch is covered by below cron plan
# Fork CI: runs on the fork's own Actions minutes when a contributor
# pushes a branch to their fork.
push:
branches:
- dev/**
# scan pull requests so findings surface on the PR instead of only post-merge
pull_request:
# Skip pushes to the maintainer branches (main, release/**, dev/**);
# upstream coverage comes from the nightly cron below. Fork feature
# branches still run.
# NOTE: do not name a local/feature branch main, release/*, or dev/* -
# its push CI would be silently skipped by this filter.
branches-ignore:
- "main"
- "release/**"
- "dev/**"
# Only the code CodeQL actually compiles (see codeql_buildscript.sh:
# wamr-compiler + product-mini/platforms/linux) plus the scan recipe and
# config. samples/** and tests/** are never built here, so scanning them
# on push would change nothing.
paths:
- ".github/workflows/codeql.yml"
- ".github/scripts/codeql_buildscript.sh"
- ".github/codeql/**"
- "build-scripts/**"
- "core/**"
- "!core/deps/**"
- "product-mini/**"
- "wamr-compiler/**"
# Upstream CI: on the first approval, re-run once - but only when the PR
# changed the CodeQL pipeline itself (gate.paths below). Regular source
# changes are covered by the nightly cron, not per-PR.
pull_request_review:
types: [submitted]
# midnight UTC on the latest commit on the main branch
schedule:
- cron: "0 0 * * *"
# allow to be triggered manually
workflow_dispatch:

# Serialize CodeQL runs per PR / branch. Only a pull_request cancels its own
# superseded run - once a PR head is replaced the old analysis is throwaway.
# Pushes to dev/** and the nightly cron are left to finish, so branch and
# scheduled scans are never dropped.
# Serialize CodeQL runs per PR / branch. Only an approval-gated review cancels
# its superseded run; feature-branch pushes and the nightly cron are left to
# finish, so branch and scheduled scans are never dropped.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request_review' }}

env:
# Merge ref to check out on approval-gated review events; empty elsewhere
# so checkout falls back to the event's default ref.
PR_REF: ${{ github.event_name == 'pull_request_review' && format('refs/pull/{0}/merge', github.event.pull_request.number) || '' }}

jobs:
# Gate the upstream approval re-run: only for a PR's first approval AND only
# when the PR touches the CodeQL pipeline definition (not general source).
gate:
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/gate.yml
with:
paths: |
.github/workflows/codeql.yml
.github/scripts/codeql_buildscript.sh
.github/scripts/codeql_fail_on_error.py
.github/codeql/**

analyze:
# Pull requests: only scan when the head branch lives in this same
# repository. A pull request from a fork runs with a read-only GITHUB_TOKEN
# (no `security-events: write`), so uploading results and reading
# code-scanning alerts is not permitted and the job would fail; skip it
# cleanly instead. Other events (push to dev/**, the nightly cron, manual
# runs) keep the original behavior of running only on the upstream repo.
# Where each trigger is meant to run:
# - push: fork CI only (a contributor's fork), on the fork's minutes.
# - schedule: upstream only (the nightly scan of the canonical repo).
# - review: upstream, first approval, only if the pipeline changed.
# - dispatch: anywhere, for manual runs on either repo.
needs: gate
if: >-
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository)
|| (github.event_name != 'pull_request' &&
github.repository == 'bytecodealliance/wasm-micro-runtime')
!cancelled() &&
(
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.repository != 'bytecodealliance/wasm-micro-runtime') ||
(github.event_name == 'schedule' && github.repository == 'bytecodealliance/wasm-micro-runtime') ||
(github.event_name == 'pull_request_review' && needs.gate.outputs.run == 'true')
)
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
Expand All @@ -63,6 +107,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v7.0.0
with:
ref: ${{ env.PR_REF }}
submodules: recursive

# Initializes the CodeQL tools for scanning.
Expand Down
55 changes: 51 additions & 4 deletions .github/workflows/coding_guidelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,59 @@
name: Coding Guidelines

on:
# will be triggered on PR events
pull_request:
# Fork CI: runs on the fork's own Actions minutes when a contributor
# pushes a branch to their fork.
push:
# Never run upstream CI on the maintainer branches: pushes to main,
# release/** and dev/** are skipped here (they are covered by the
# approval-gated PR review below). Fork feature branches still run.
# NOTE: do not name a local/feature branch main, release/*, or dev/* -
# its push CI would be silently skipped by this filter.
branches-ignore:
- "main"
- "release/**"
- "dev/**"
# Upstream CI: only spend upstream minutes once a PR is approved
# (first approval only, enforced by the gate job below).
pull_request_review:
types: [submitted]
# allow to be triggered manually
workflow_dispatch:

# Cancel any in-flight jobs for the same PR/branch so there's only one active
# at a time
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
# Merge ref to check out on approval-gated review events; empty elsewhere
# so checkout falls back to the event's default ref.
PR_REF: ${{ github.event_name == 'pull_request_review' && format('refs/pull/{0}/merge', github.event.pull_request.number) || '' }}

jobs:
# Gate upstream minutes: always runs (a few-second no-op on push/dispatch)
# and outputs `run`, "true" only for a PR's first approval. It must NOT be
# skipped: a skipped job propagates the skip down the needs chain to every
# descendant, even ones that broke the chain with their own `if`.
gate:
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/gate.yml

compliance_job:
needs: gate
if: ${{ !cancelled() && (github.event_name != 'pull_request_review' || needs.gate.outputs.run == 'true') }}
runs-on: ubuntu-24.04
steps:
- name: checkout
uses: actions/checkout@v7.0.0
with:
ref: ${{ env.PR_REF }}
fetch-depth: 0

- name: install clang-format-21
Expand All @@ -44,4 +76,19 @@ jobs:

# github.event.pull_request.base.label = ${{github.repository}}/${{github.base_ref}}
- name: Run Coding Guidelines Checks
run: /usr/bin/env python3 ./ci/coding_guidelines_check.py --commits ${{ github.event.pull_request.base.sha }}..HEAD
run: |
# pull_request_review: base.sha is the PR base commit.
# push: github.event.before is the previous tip, but it is the zero
# SHA on a branch's first push / after a force-push, which would make
# `<zero>..HEAD` fail. Fall back to the merge-base with origin/main.
base="${{ github.event.pull_request.base.sha }}"
if [ -z "$base" ]; then
before="${{ github.event.before }}"
if [ -z "$before" ] || [ "$before" = "0000000000000000000000000000000000000000" ]; then
git fetch --quiet origin main
base=$(git merge-base origin/main HEAD)
else
base="$before"
fi
fi
/usr/bin/env python3 ./ci/coding_guidelines_check.py --commits "$base..HEAD"
Loading