Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9bfa038
upload test results to codecov
nashif Sep 1, 2025
843afbb
limit to kernel tests
nashif Sep 1, 2025
dc6e063
only on push
nashif Sep 1, 2025
4f221d7
Revert "limit to kernel tests"
nashif Sep 1, 2025
bddef56
scripts/get_maintainer.py: find area by name
nashif Sep 2, 2025
8721d8f
scripts/set_assignees.py: also set assignee on manifest changes
nashif Sep 2, 2025
ec786c0
ci: add commit range argument
nashif Sep 2, 2025
56226b0
init west
nashif Sep 2, 2025
8c8c1a9
logging
nashif Sep 2, 2025
9fe72ce
some debug
nashif Sep 2, 2025
5957f41
sha
nashif Sep 2, 2025
10eefa8
split workflow
nashif Sep 5, 2025
a587b90
fix layout
nashif Sep 5, 2025
6e728fc
fix layout
nashif Sep 5, 2025
5040c2c
fix versions
nashif Sep 5, 2025
82f0fdb
token
nashif Sep 5, 2025
c383c27
west setup
nashif Sep 5, 2025
68ccdf0
wd
nashif Sep 5, 2025
25ec04b
dump manifest
nashif Sep 5, 2025
cd50518
conflicting -m
nashif Sep 5, 2025
18641b8
pr
nashif Sep 5, 2025
4e95b63
pr
nashif Sep 5, 2025
626b681
event
nashif Sep 5, 2025
a27517c
set
nashif Sep 5, 2025
c6eddd9
event
nashif Sep 5, 2025
d7e004f
space
nashif Sep 5, 2025
a24f443
workflow
nashif Sep 5, 2025
ceba4ab
validate nr
nashif Sep 5, 2025
43e1bfc
workflow
nashif Sep 5, 2025
7600778
checkout
nashif Sep 5, 2025
47fe9ff
load areas
nashif Sep 5, 2025
1a92761
handle manifest areas
nashif Sep 5, 2025
18489ea
handle areas
nashif Sep 5, 2025
8ac8d6b
fix arg
nashif Sep 5, 2025
8a370b7
parsed
nashif Sep 5, 2025
de2992a
dummy change
nashif Sep 2, 2025
4c377c0
change cmsis
nashif Sep 2, 2025
ea3c74e
another change
nashif Sep 2, 2025
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
83 changes: 83 additions & 0 deletions .github/workflows/assigner-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Pull Request Assigner Completion Workflow

# read-write repo token
# access to secrets
on:
workflow_run:
workflows: ["Pull Request Assigner"]
types:
- completed

permissions:
contents: read

jobs:
assignment:
name: Pull Request Assignment
runs-on: ubuntu-24.04
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'

steps:
- name: Check out source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false
- name: Download artifacts
id: download-artifacts
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
with:
workflow: assigner.yml
run_id: ${{ github.event.workflow_run.id }}
if_no_artifact_found: ignore

- name: Load PR number
if: steps.download-artifacts.outputs.found_artifact == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
let fs = require("fs");
let pr_number = Number(fs.readFileSync("./pr/NR"));
core.exportVariable("PR_NUM", pr_number);

- name: Check PR number
if: steps.download-artifacts.outputs.found_artifact == 'true'
id: check-pr
uses: carpentries/actions/check-valid-pr@2e20fd5ee53b691e27455ce7ca3b16ea885140e8 # v0.15.0
with:
pr: ${{ env.PR_NUM }}
sha: ${{ github.event.workflow_run.head_sha }}

- name: Validate PR number
if: |
steps.download-artifacts.outputs.found_artifact == 'true' &&
steps.check-pr.outputs.VALID != 'true'
run: |
echo "ABORT: PR number validation failed!"
exit 1


- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.12
cache: pip
cache-dependency-path: scripts/requirements-actions.txt

- name: Install Python packages
run: |
pip install -r scripts/requirements-actions.txt --require-hashes

- name: Run assignment script
env:
GITHUB_TOKEN: ${{ secrets.ZB_PR_ASSIGNER_GITHUB_TOKEN }}
run: |
if [ -f "./pr/manifest_areas.json" ]; then
ARGS="--areas ./pr/manifest_areas.json"
else
ARGS=""
fi
python3 scripts/set_assignees.py -P ${{ env.PR_NUM }} -M MAINTAINERS.yml -v \
--repo ${{ github.event.repository.name }} ${ARGS}
86 changes: 57 additions & 29 deletions .github/workflows/assigner.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Pull Request Assigner

on:
pull_request_target:
pull_request:
types:
- opened
- synchronize
Expand All @@ -28,37 +28,65 @@ jobs:
issues: write # to add assignees to issues

steps:
- name: Check out source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check out source code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.12
cache: pip
cache-dependency-path: scripts/requirements-actions.txt
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.12
cache: pip
cache-dependency-path: scripts/requirements-actions.txt

- name: Install Python packages
run: |
pip install -r scripts/requirements-actions.txt --require-hashes
- name: Install Python packages
run: |
pip install -r scripts/requirements-actions.txt --require-hashes

- name: Run assignment script
env:
GITHUB_TOKEN: ${{ secrets.ZB_PR_ASSIGNER_GITHUB_TOKEN }}
run: |
FLAGS="-v"
FLAGS+=" -o ${{ github.event.repository.owner.login }}"
FLAGS+=" -r ${{ github.event.repository.name }}"
FLAGS+=" -M MAINTAINERS.yml"
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
FLAGS+=" -P ${{ github.event.pull_request.number }}"
elif [ "${{ github.event_name }}" = "issues" ]; then
- name: west setup
if: >
github.event_name == 'pull_request'
run: |
git config --global user.email "[email protected]"
git config --global user.name "Your Name"
west init -l . || true
mkdir -p ./pr

- name: Run assignment script
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
FLAGS="-v"
FLAGS+=" -o ${{ github.event.repository.owner.login }}"
FLAGS+=" -r ${{ github.event.repository.name }}"
FLAGS+=" -M MAINTAINERS.yml"
if [ "${{ github.event_name }}" = "pull_request" ]; then
FLAGS+=" -P ${{ github.event.pull_request.number }} --manifest -c origin/${{ github.base_ref }}.."
python3 scripts/set_assignees.py $FLAGS
cp -f manifest_areas.json ./pr/
elif [ "${{ github.event_name }}" = "issues" ]; then
FLAGS+=" -I ${{ github.event.issue.number }}"
elif [ "${{ github.event_name }}" = "schedule" ]; then
python3 scripts/set_assignees.py $FLAGS
elif [ "${{ github.event_name }}" = "schedule" ]; then
FLAGS+=" --modules"
else
echo "Unknown event: ${{ github.event_name }}"
exit 1
fi
python3 scripts/set_assignees.py $FLAGS
else
echo "Unknown event: ${{ github.event_name }}"
exit 1
fi


- name: Save PR number
if: >
github.event_name == 'pull_request'
run: |
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: >
github.event_name == 'pull_request'
with:
name: pr
path: pr/

python3 scripts/set_assignees.py $FLAGS
6 changes: 6 additions & 0 deletions .github/workflows/twister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ jobs:
junit.html
junit.xml

- name: Upload test results to Codecov
if: ${{ !cancelled() && (github.event_name == 'push') }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}

- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@3a74b2957438d0b6e2e61d67b05318aa25c9e6c6 # v2.20.0
with:
Expand Down
26 changes: 26 additions & 0 deletions scripts/get_maintainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ def _parse_args():
nargs="?",
help="List all areas maintained by maintainer.")


area_parser = subparsers.add_parser(
"area",
help="List area(s) by name")
area_parser.add_argument(
"name",
metavar="AREA",
nargs="?",
help="List all areas with the given name.")

area_parser.set_defaults(cmd_fn=Maintainers._area_cmd)

# New arguments for filtering
areas_parser.add_argument(
"--without-maintainers",
Expand Down Expand Up @@ -220,6 +232,12 @@ def __init__(self, filename=None):

self.areas[area_name] = area

def name2areas(self, name):
"""
Returns a list of Area instances for the areas that match 'name'.
"""
return [area for area in self.areas.values() if area.name == name]

def path2areas(self, path):
"""
Returns a list of Area instances for the areas that contain 'path',
Expand Down Expand Up @@ -262,6 +280,14 @@ def __repr__(self):
# Command-line subcommands
#

def _area_cmd(self, args):
# 'area' subcommand implementation

res = set()
areas = self.name2areas(args.name)
res.update(areas)
_print_areas(res)

def _path_cmd(self, args):
# 'path' subcommand implementation

Expand Down
94 changes: 86 additions & 8 deletions scripts/set_assignees.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
import os
import time
import datetime
import json
from github import Github, GithubException
from github.GithubException import UnknownObjectException
from collections import defaultdict
from west.manifest import Manifest
from west.manifest import ManifestProject
from git import Repo
from pathlib import Path

TOP_DIR = os.path.join(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(TOP_DIR, "scripts"))
from get_maintainer import Maintainers

zephyr_base = os.getenv('ZEPHYR_BASE', os.path.join(TOP_DIR, '..'))

def log(s):
if args.verbose > 0:
print(s, file=sys.stdout)
Expand Down Expand Up @@ -50,11 +55,73 @@ def parse_args():
parser.add_argument("-r", "--repo", default="zephyr",
help="Github repository")

parser.add_argument("-c", "--commits", default=None,
help="Commit range in the form: a..b")

parser.add_argument("--manifest", action="store_true", default=False,
help="Dump manifest changes")

parser.add_argument("--areas", default=None,
help="Load list of areas from file generated by --manifest")

parser.add_argument("-v", "--verbose", action="count", default=0,
help="Verbose Output")

args = parser.parse_args()


def process_manifest():
log("Processing manifest changes")
repo = Repo(zephyr_base)
old_manifest_content = repo.git.show(f"{args.commits[:-2]}:west.yml")
with open("west_old.yml", "w") as manifest:
manifest.write(old_manifest_content)
old_manifest = Manifest.from_file("west_old.yml")
new_manifest = Manifest.from_file("west.yml")
old_projs = set((p.name, p.revision) for p in old_manifest.projects)
new_projs = set((p.name, p.revision) for p in new_manifest.projects)
# Removed projects
rprojs = set(filter(lambda p: p[0] not in list(p[0] for p in new_projs),
old_projs - new_projs))
# Updated projects
uprojs = set(filter(lambda p: p[0] in list(p[0] for p in old_projs),
new_projs - old_projs))
# Added projects
aprojs = new_projs - old_projs - uprojs

# All projs
projs = rprojs | uprojs | aprojs
projs_names = [name for name, rev in projs]

log(f"found modified projects: {projs_names}")
areas = []
for p in projs_names:
areas.append(f'West project: {p}')

log(f'manifest areas: {areas}')
return areas


def dump_manifest_changes(gh, maintainer_file, number):
gh_repo = gh.get_repo(f"{args.org}/{args.repo}")
pr = gh_repo.get_pull(number)
fn = list(pr.get_files())
areas = []
for changed_file in fn:
log(f"file: {changed_file.filename}")

if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
changed_areas = process_manifest()
for _area in changed_areas:
area_match = maintainer_file.name2areas(_area)
if area_match:
areas.extend(area_match)

log(f"Areas: {areas}")
# now dump the list of areas into a json file
with open("manifest_areas.json", "w") as f:
json.dump([area.name for area in areas], f, indent=4)

def process_pr(gh, maintainer_file, number):

gh_repo = gh.get_repo(f"{args.org}/{args.repo}")
Expand All @@ -67,13 +134,8 @@ def process_pr(gh, maintainer_file, number):
found_maintainers = defaultdict(int)

num_files = 0
all_areas = set()
fn = list(pr.get_files())

for changed_file in fn:
if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
break

if pr.commits == 1 and (pr.additions <= 1 and pr.deletions <= 1):
labels = {'size: XS'}

Expand All @@ -82,14 +144,28 @@ def process_pr(gh, maintainer_file, number):
return

for changed_file in fn:

num_files += 1
log(f"file: {changed_file.filename}")
areas = maintainer_file.path2areas(changed_file.filename)

areas = []
if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
if args.areas and Path(args.areas).is_file():
with open(args.areas, "r") as f:
parsed_areas = json.load(f)
for _area in parsed_areas:
area_match = maintainer_file.name2areas(_area)
if area_match:
areas.extend(area_match)
else:
log(f"Manifest changes detected but no --areas file specified, skipping...")
continue
else:
areas = maintainer_file.path2areas(changed_file.filename)

if not areas:
continue

all_areas.update(areas)
is_instance = False
sorted_areas = sorted(areas, key=lambda x: 'Platform' in x.name, reverse=True)
for area in sorted_areas:
Expand Down Expand Up @@ -358,7 +434,9 @@ def main():
gh = Github(token)
maintainer_file = Maintainers(args.maintainer_file)

if args.pull_request:
if args.pull_request and args.manifest:
dump_manifest_changes(gh, maintainer_file, args.pull_request)
elif args.pull_request:
process_pr(gh, maintainer_file, args.pull_request)
elif args.issue:
process_issue(gh, maintainer_file, args.issue)
Expand Down
Loading
Loading