Skip to content

Commit a1a6d52

Browse files
committed
scripts/set_assignees.py: also set assignee on manifest changes
Parse manifest for changes and set assignees for any manifest entries that has changed. Signed-off-by: Anas Nashif <[email protected]>
1 parent e7b61ba commit a1a6d52

File tree

3 files changed

+223
-37
lines changed

3 files changed

+223
-37
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Pull Request Assigner Completion Workflow
2+
3+
# read-write repo token
4+
# access to secrets
5+
on:
6+
workflow_run:
7+
workflows: ["Pull Request Assigner"]
8+
types:
9+
- completed
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
assignment:
16+
name: Pull Request Assignment
17+
runs-on: ubuntu-24.04
18+
if: >
19+
github.event.workflow_run.event == 'pull_request' &&
20+
github.event.workflow_run.conclusion == 'success'
21+
22+
steps:
23+
- name: Check out source code
24+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25+
with:
26+
fetch-depth: 0
27+
persist-credentials: false
28+
- name: Download artifacts
29+
id: download-artifacts
30+
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
31+
with:
32+
workflow: assigner.yml
33+
run_id: ${{ github.event.workflow_run.id }}
34+
if_no_artifact_found: ignore
35+
36+
- name: Load PR number
37+
if: steps.download-artifacts.outputs.found_artifact == 'true'
38+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
39+
with:
40+
script: |
41+
let fs = require("fs");
42+
let pr_number = Number(fs.readFileSync("./pr/NR"));
43+
core.exportVariable("PR_NUM", pr_number);
44+
45+
- name: Check PR number
46+
if: steps.download-artifacts.outputs.found_artifact == 'true'
47+
id: check-pr
48+
uses: carpentries/actions/check-valid-pr@2e20fd5ee53b691e27455ce7ca3b16ea885140e8 # v0.15.0
49+
with:
50+
pr: ${{ env.PR_NUM }}
51+
sha: ${{ github.event.workflow_run.head_sha }}
52+
53+
- name: Validate PR number
54+
if: |
55+
steps.download-artifacts.outputs.found_artifact == 'true' &&
56+
steps.check-pr.outputs.VALID != 'true'
57+
run: |
58+
echo "ABORT: PR number validation failed!"
59+
exit 1
60+
61+
62+
- name: Set up Python
63+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
64+
with:
65+
python-version: 3.12
66+
cache: pip
67+
cache-dependency-path: scripts/requirements-actions.txt
68+
69+
- name: Install Python packages
70+
run: |
71+
pip install -r scripts/requirements-actions.txt --require-hashes
72+
73+
- name: Run assignment script
74+
env:
75+
GITHUB_TOKEN: ${{ secrets.ZB_PR_ASSIGNER_GITHUB_TOKEN }}
76+
run: |
77+
if [ -f "./pr/manifest_areas.json" ]; then
78+
ARGS="--areas ./pr/manifest_areas.json"
79+
else
80+
ARGS=""
81+
fi
82+
python3 scripts/set_assignees.py -P ${{ env.PR_NUM }} -M MAINTAINERS.yml -v \
83+
--repo ${{ github.event.repository.name }} ${ARGS}

.github/workflows/assigner.yml

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Pull Request Assigner
22

33
on:
4-
pull_request_target:
4+
pull_request:
55
types:
66
- opened
77
- synchronize
@@ -28,37 +28,62 @@ jobs:
2828
issues: write # to add assignees to issues
2929

3030
steps:
31-
- name: Check out source code
32-
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
31+
- name: Check out source code
32+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
3333

34-
- name: Set up Python
35-
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
36-
with:
37-
python-version: 3.12
38-
cache: pip
39-
cache-dependency-path: scripts/requirements-actions.txt
34+
- name: Set up Python
35+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
36+
with:
37+
python-version: 3.12
38+
cache: pip
39+
cache-dependency-path: scripts/requirements-actions.txt
4040

41-
- name: Install Python packages
42-
run: |
43-
pip install -r scripts/requirements-actions.txt --require-hashes
41+
- name: Install Python packages
42+
run: |
43+
pip install -r scripts/requirements-actions.txt --require-hashes
4444
45-
- name: Run assignment script
46-
env:
47-
GITHUB_TOKEN: ${{ secrets.ZB_PR_ASSIGNER_GITHUB_TOKEN }}
48-
run: |
49-
FLAGS="-v"
50-
FLAGS+=" -o ${{ github.event.repository.owner.login }}"
51-
FLAGS+=" -r ${{ github.event.repository.name }}"
52-
FLAGS+=" -M MAINTAINERS.yml"
53-
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
54-
FLAGS+=" -P ${{ github.event.pull_request.number }}"
55-
elif [ "${{ github.event_name }}" = "issues" ]; then
45+
- name: west setup
46+
if: >
47+
github.event_name == 'pull_request'
48+
run: |
49+
git config --global user.email "[email protected]"
50+
git config --global user.name "Your Name"
51+
west init -l . || true
52+
mkdir -p ./pr
53+
54+
- name: Run assignment script
55+
env:
56+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57+
run: |
58+
FLAGS="-v"
59+
FLAGS+=" -o ${{ github.event.repository.owner.login }}"
60+
FLAGS+=" -r ${{ github.event.repository.name }}"
61+
FLAGS+=" -M MAINTAINERS.yml"
62+
if [ "${{ github.event_name }}" = "pull_request" ]; then
63+
FLAGS+=" -P ${{ github.event.pull_request.number }} --manifest -c origin/${{ github.base_ref }}.."
64+
python3 scripts/set_assignees.py $FLAGS
65+
cp -f manifest_areas.json ./pr/
66+
elif [ "${{ github.event_name }}" = "issues" ]; then
5667
FLAGS+=" -I ${{ github.event.issue.number }}"
57-
elif [ "${{ github.event_name }}" = "schedule" ]; then
68+
python3 scripts/set_assignees.py $FLAGS
69+
elif [ "${{ github.event_name }}" = "schedule" ]; then
5870
FLAGS+=" --modules"
59-
else
60-
echo "Unknown event: ${{ github.event_name }}"
61-
exit 1
62-
fi
71+
python3 scripts/set_assignees.py $FLAGS
72+
else
73+
echo "Unknown event: ${{ github.event_name }}"
74+
exit 1
75+
fi
76+
77+
78+
- name: Save PR number
79+
if: >
80+
github.event_name == 'pull_request'
81+
run: |
82+
echo ${{ github.event.number }} > ./pr/NR
83+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
84+
if: >
85+
github.event_name == 'pull_request'
86+
with:
87+
name: pr
88+
path: pr/
6389

64-
python3 scripts/set_assignees.py $FLAGS

scripts/set_assignees.py

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@
88
import os
99
import time
1010
import datetime
11+
import json
1112
from github import Github, GithubException
1213
from github.GithubException import UnknownObjectException
1314
from collections import defaultdict
1415
from west.manifest import Manifest
1516
from west.manifest import ManifestProject
17+
from git import Repo
18+
from pathlib import Path
1619

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

24+
zephyr_base = os.getenv('ZEPHYR_BASE', os.path.join(TOP_DIR, '..'))
25+
2126
def log(s):
2227
if args.verbose > 0:
2328
print(s, file=sys.stdout)
@@ -50,11 +55,73 @@ def parse_args():
5055
parser.add_argument("-r", "--repo", default="zephyr",
5156
help="Github repository")
5257

58+
parser.add_argument("-c", "--commits", default=None,
59+
help="Commit range in the form: a..b")
60+
61+
parser.add_argument("--manifest", action="store_true", default=False,
62+
help="Dump manifest changes")
63+
64+
parser.add_argument("--areas", default=None,
65+
help="Load list of areas from file generated by --manifest")
66+
5367
parser.add_argument("-v", "--verbose", action="count", default=0,
5468
help="Verbose Output")
5569

5670
args = parser.parse_args()
5771

72+
73+
def process_manifest():
74+
log("Processing manifest changes")
75+
repo = Repo(zephyr_base)
76+
old_manifest_content = repo.git.show(f"{args.commits[:-2]}:west.yml")
77+
with open("west_old.yml", "w") as manifest:
78+
manifest.write(old_manifest_content)
79+
old_manifest = Manifest.from_file("west_old.yml")
80+
new_manifest = Manifest.from_file("west.yml")
81+
old_projs = set((p.name, p.revision) for p in old_manifest.projects)
82+
new_projs = set((p.name, p.revision) for p in new_manifest.projects)
83+
# Removed projects
84+
rprojs = set(filter(lambda p: p[0] not in list(p[0] for p in new_projs),
85+
old_projs - new_projs))
86+
# Updated projects
87+
uprojs = set(filter(lambda p: p[0] in list(p[0] for p in old_projs),
88+
new_projs - old_projs))
89+
# Added projects
90+
aprojs = new_projs - old_projs - uprojs
91+
92+
# All projs
93+
projs = rprojs | uprojs | aprojs
94+
projs_names = [name for name, rev in projs]
95+
96+
log(f"found modified projects: {projs_names}")
97+
areas = []
98+
for p in projs_names:
99+
areas.append(f'West project: {p}')
100+
101+
log(f'manifest areas: {areas}')
102+
return areas
103+
104+
105+
def dump_manifest_changes(gh, maintainer_file, number):
106+
gh_repo = gh.get_repo(f"{args.org}/{args.repo}")
107+
pr = gh_repo.get_pull(number)
108+
fn = list(pr.get_files())
109+
areas = []
110+
for changed_file in fn:
111+
log(f"file: {changed_file.filename}")
112+
113+
if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
114+
changed_areas = process_manifest()
115+
for _area in changed_areas:
116+
area_match = maintainer_file.name2areas(_area)
117+
if area_match:
118+
areas.extend(area_match)
119+
120+
log(f"Areas: {areas}")
121+
# now dump the list of areas into a json file
122+
with open("manifest_areas.json", "w") as f:
123+
json.dump([area.name for area in areas], f, indent=4)
124+
58125
def process_pr(gh, maintainer_file, number):
59126

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

69136
num_files = 0
70-
all_areas = set()
71137
fn = list(pr.get_files())
72138

73-
for changed_file in fn:
74-
if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
75-
break
76-
77139
if pr.commits == 1 and (pr.additions <= 1 and pr.deletions <= 1):
78140
labels = {'size: XS'}
79141

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

84146
for changed_file in fn:
147+
85148
num_files += 1
86149
log(f"file: {changed_file.filename}")
87-
areas = maintainer_file.path2areas(changed_file.filename)
150+
151+
areas = []
152+
if changed_file.filename in ['west.yml','submanifests/optional.yaml']:
153+
if args.areas and Path(args.areas).is_file():
154+
with open(args.areas, "r") as f:
155+
parsed_areas = json.load(f)
156+
for _area in parsed_areas:
157+
area_match = maintainer_file.name2areas(_area)
158+
if area_match:
159+
areas.extend(area_match)
160+
else:
161+
log(f"Manifest changes detected but no --areas file specified, skipping...")
162+
continue
163+
else:
164+
areas = maintainer_file.path2areas(changed_file.filename)
88165

89166
if not areas:
90167
continue
91168

92-
all_areas.update(areas)
93169
is_instance = False
94170
sorted_areas = sorted(areas, key=lambda x: 'Platform' in x.name, reverse=True)
95171
for area in sorted_areas:
@@ -358,7 +434,9 @@ def main():
358434
gh = Github(token)
359435
maintainer_file = Maintainers(args.maintainer_file)
360436

361-
if args.pull_request:
437+
if args.pull_request and args.manifest:
438+
dump_manifest_changes(gh, maintainer_file, args.pull_request)
439+
elif args.pull_request:
362440
process_pr(gh, maintainer_file, args.pull_request)
363441
elif args.issue:
364442
process_issue(gh, maintainer_file, args.issue)

0 commit comments

Comments
 (0)