summaryrefslogtreecommitdiffstats
path: root/scripts/b4-wrapper-poky.py
blob: ec6568eb9b7cab389178c08173a43caa2fc6bd48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/usr/bin/env python3
#
# Copyright OpenEmbedded Contributors
#
# SPDX-License-Identifier: MIT
#
# This script is to be called by b4:
# - through the b4.prep-perpatch-check-cmd with "prep-perpatch-check-cmd" as
#   first argument,
# - through b4.send-auto-cc-cmd with "send-auto-cc-cmd" as first argument,
# - through b4.send-auto-to-cmd with "send-auto-to-cmd" as first argument,
#
# When prep-perpatch-check-cmd is passsed:
#
#  This checks that a patch makes changes to at most one project in the poky
#  combo repo (that is, out of yocto-docs, bitbake, openembedded-core combined
#  into poky and the poky-specific files).
#
#  Printing something to stdout in this file will result in b4 prep --check fail
#  for the currently parsed patch.
#
#  It checks that all patches in the series make changes to at most one project.
#
# When send-auto-cc-cmd is passed:
#
#  This returns the list of Cc recipients for a patch.
#
# When send-auto-to-cmd is passed:
#
#  This returns the list of To recipients for a patch.
#
# This script takes as stdin a patch.

import pathlib
import re
import shutil
import subprocess
import sys

cmd = sys.argv[1]

patch = sys.stdin.readlines()

# Subject field is used to identify the last patch as this script is called for
# each patch. We edit the same file in a series by using the References field
# unique identifier to check which projects are modified by earlier patches in
# the series. To avoid cluttering the disk, the last patch in the list removes
# that shared file.
re_subject = re.compile(r'^Subject:.*\[.*PATCH.*\s(\d+)/\1')
re_ref = re.compile(r'^References: <(.*)>$')

subject = None
ref = None

if not shutil.which("lsdiff"):
    print("lsdiff missing from host, please install patchutils")
    sys.exit(-1)

try:
    one_patch_series = False
    for line in patch:
        subject = re_subject.match(line)
        if subject:
            # Handle [PATCH 1/1]
            if subject.group(1) == 1:
                one_patch_series = True
            break
        if re.match(r'^Subject: .*\[.*PATCH[^/]*\]', line):
            # Single patch is named [PATCH] but if there are prefix, it could be
            # [PATCH prefix], so handle everything that doesn't have a /
            # character which is used as separator between current patch number
            # and total patch number
            one_patch_series = True
            break

    if cmd == "prep-perpatch-check-cmd" and not one_patch_series:
        for line in patch:
            ref = re_ref.match(line)
            if ref:
                break

        if not ref:
            print("Failed to find ref to cover letter (References:)...")
            sys.exit(-2)

        ref = ref.group(1)
        series_check = pathlib.Path(f".tmp-{ref}")

    patch = "".join(patch)

    if cmd == "send-auto-cc-cmd":
        # Patches to BitBake documentation should also go to yocto-docs mailing list
        project_paths = {
                "yocto-docs": ["bitbake/doc/*"],
        }
    else:
        project_paths = {
                "bitbake": ["bitbake/*"],
                "yocto-docs": ["documentation/*"],
                "poky": [
                    "meta-poky/*",
                    "meta-yocto-bsp/*",
                    "README.hardware.md",
                    "README.poky.md",
                    # scripts/b4-wrapper-poky.py is only run by b4 when in poky
                    # git repo. With that limitation, changes made to .b4-config
                    # can only be for poky's and not OE-Core's as only poky's is
                    # stored in poky git repo.
                    ".b4-config",
                    ],
        }

    # List of projects touched by this patch
    projs = []

    # Any file not matched by any path in project_paths means it is from
    # OE-Core.
    # When matching some path in project_paths, remove the matched files from
    # that list.
    files_left = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"],
                                         input=patch, text=True)
    files_left = set(files_left)

    for proj, proj_paths in project_paths.items():
        lsdiff_args = [f"--include={path}" for path in proj_paths]
        files = subprocess.check_output(["lsdiff", "--strip-match=1", "--strip=1"] + lsdiff_args,
                                        input=patch, text=True)
        if len(files):
            files_left = files_left - set(files)
            projs.append(proj)
            continue

        # Handle patches made with --no-prefix
        files = subprocess.check_output(["lsdiff"] + lsdiff_args,
                                        input=patch, text=True)
        if len(files):
            files_left = files_left - set(files)
            projs.append(proj)

    # Catch-all for everything not poky-specific or in bitbake/yocto-docs
    if len(files_left) and cmd != "send-auto-cc-cmd":
        projs.append("openembedded-core")

    if cmd == "prep-perpatch-check-cmd":
        if len(projs) > 1:
            print(f"Diff spans more than one project ({', '.join(sorted(projs))}), split into multiple commits...")
            sys.exit(-3)

        # No need to check other patches in the series as there aren't any
        if one_patch_series:
            sys.exit(0)

        # This should be replaced once b4 supports prep-perseries-check-cmd (or something similar)

        if series_check.exists():
            # NOT race-free if b4 decides to parallelize prep-perpatch-check-cmd
            series_projs = series_check.read_text().split('\n')
        else:
            series_projs = []

        series_projs += projs
        uniq_series_projs = set(series_projs)
        # NOT race-free, if b4 decides to parallelize prep-perpatch-check-cmd
        series_check.write_text('\n'.join(uniq_series_projs))

        if len(uniq_series_projs) > 1:
            print(f"Series spans more than one project ({', '.join(sorted(uniq_series_projs))}), split into multiple series...")
            sys.exit(-4)
    else:  # send-auto-cc-cmd / send-auto-to-cmd
        ml_projs = {
            "bitbake": "bitbake-devel@lists.openembedded.org",
            "yocto-docs": "docs@lists.yoctoproject.org",
            "poky": "poky@lists.yoctoproject.org",
            "openembedded-core": "openembedded-core@lists.openembedded.org",
        }

        print("\n".join([ml_projs[ml] for ml in projs]))

    sys.exit(0)
finally:
    # Last patch in the series, cleanup tmp file
    if subject and ref and series_check.exists():
        series_check.unlink()