diff mbox series

[RFC] add basic support for b4 contribution workflow

Message ID 20250123-b4-support-v1-1-4841f52eccbb@cherry.de (mailing list archive)
State New
Headers show
Series [RFC] add basic support for b4 contribution workflow | expand

Commit Message

Quentin Schulz Jan. 23, 2025, 3:44 p.m. UTC
From: Quentin Schulz <quentin.schulz@cherry.de>

b4[1] is a very nice tool for mail-based contribution. A config[2] file
exists to set up a few defaults. We can use it to set the Cc recipients
to always add, in our case the mailing list.

This also adds a wrapper script that is called by b4 to check that each
patch in the series is only for one project. Indeed, poky is actually
a "collection" of multiple repositories, namely BitBake,
OpenEmbedded-Core and the Yocto Docs. One patch should therefore not
make changes in multiple of those projects otherwise it cannot be
merged.
Additionally, a check is added to make sure that a series only touches
files from one project to avoid having to figure out which patch is to
be merged by which maintainer in which project repo.

Moreover, it is not uncommon to have people develop patches for those
projects from within poky. This wrapper figures out which mailing lists
to send patches to based on the files that are modified in the series.
Considering that patches to the bitbake/doc/ directory need to be sent
to both the bitbake and yocto-docs mailing list, this wrapper handles
that. A limitation of the script (lsdiff actually) is that it doesn't
know how to handle empty files, but those should be of rather rare
occurrences.

Note that this script requires hardcoding of paths that are handled by
different projects to map files to projects. Anything not mapped is
assumed part of OE-Core.

[1] https://pypi.org/project/b4/
[2] https://b4.docs.kernel.org/en/latest/config.html

Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
---
This is marked as RFC because I believe there's some additional plumbing
required to make this work reliably.

Indeed, OE-Core will also get its own .b4-config,
c.f. https://lore.kernel.org/openembedded-core/20250117-b4-support-v1-1-fe74a5f50eb6@cherry.de/T/#u

So the same file will exist in both OE-Core and poky, at the same
location. The one from poky should always be this one (or with changes
contributed via the poky mailing list) and not overridden during a merge
by an update from the one in OE-Core. Not sure how to handle that yet
though, does anyone know how this merging of trees is done in poky and
what can be done to make this safe?

There's also a decision that was made to not allow patches to different
projects in a series, but maybe that's not desired.

Finally, I'm not sure about the appropriate copyright holder for the
patch and the license, I personally don't care, so up to you.
---
 .b4-config    |   3 ++
 b4-wrapper.py | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 165 insertions(+)


---
base-commit: e59da05be4365c4922e880e07a81549927cd92be
change-id: 20250117-b4-support-e4cca7ad3106

Best regards,

Comments

Richard Purdie Jan. 24, 2025, 5:48 p.m. UTC | #1
On Thu, 2025-01-23 at 16:44 +0100, Quentin Schulz via lists.yoctoproject.org wrote:
> From: Quentin Schulz <quentin.schulz@cherry.de>
> 
> b4[1] is a very nice tool for mail-based contribution. A config[2] file
> exists to set up a few defaults. We can use it to set the Cc recipients
> to always add, in our case the mailing list.
> 
> This also adds a wrapper script that is called by b4 to check that each
> patch in the series is only for one project. Indeed, poky is actually
> a "collection" of multiple repositories, namely BitBake,
> OpenEmbedded-Core and the Yocto Docs. One patch should therefore not
> make changes in multiple of those projects otherwise it cannot be
> merged.
> Additionally, a check is added to make sure that a series only touches
> files from one project to avoid having to figure out which patch is to
> be merged by which maintainer in which project repo.
> 
> Moreover, it is not uncommon to have people develop patches for those
> projects from within poky. This wrapper figures out which mailing lists
> to send patches to based on the files that are modified in the series.
> Considering that patches to the bitbake/doc/ directory need to be sent
> to both the bitbake and yocto-docs mailing list, this wrapper handles
> that. A limitation of the script (lsdiff actually) is that it doesn't
> know how to handle empty files, but those should be of rather rare
> occurrences.
> 
> Note that this script requires hardcoding of paths that are handled by
> different projects to map files to projects. Anything not mapped is
> assumed part of OE-Core.
> 
> [1] https://pypi.org/project/b4/
> [2] https://b4.docs.kernel.org/en/latest/config.html
> 
> Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
> ---
> This is marked as RFC because I believe there's some additional plumbing
> required to make this work reliably.
> 
> Indeed, OE-Core will also get its own .b4-config,
> c.f. https://lore.kernel.org/openembedded-core/20250117-b4-support-v1-1-fe74a5f50eb6@cherry.de/T/#u
> 
> So the same file will exist in both OE-Core and poky, at the same
> location. The one from poky should always be this one (or with changes
> contributed via the poky mailing list) and not overridden during a merge
> by an update from the one in OE-Core. Not sure how to handle that yet
> though, does anyone know how this merging of trees is done in poky and
> what can be done to make this safe?

I can sort that bit. Basically we mark the config file as not being
synced by combo-layer.

> There's also a decision that was made to not allow patches to different
> projects in a series, but maybe that's not desired.
> 
> Finally, I'm not sure about the appropriate copyright holder for the
> patch and the license, I personally don't care, so up to you.

Those look fine to me.

> ---
>  .b4-config    |   3 ++
>  b4-wrapper.py | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 165 insertions(+)

I'm not against this but I would like to:

a) Put the script into OE-Core, just so that we don't have "magic"
things in poky.
b) Perhaps name it something a little more poky specific
c) Move it into the scripts subdir

Cheers,

Richard
Quentin Schulz Jan. 24, 2025, 6:06 p.m. UTC | #2
Hi Richard,

On 1/24/25 6:48 PM, Richard Purdie wrote:
> On Thu, 2025-01-23 at 16:44 +0100, Quentin Schulz via lists.yoctoproject.org wrote:
>> From: Quentin Schulz <quentin.schulz@cherry.de>
>>
>> b4[1] is a very nice tool for mail-based contribution. A config[2] file
>> exists to set up a few defaults. We can use it to set the Cc recipients
>> to always add, in our case the mailing list.
>>
>> This also adds a wrapper script that is called by b4 to check that each
>> patch in the series is only for one project. Indeed, poky is actually
>> a "collection" of multiple repositories, namely BitBake,
>> OpenEmbedded-Core and the Yocto Docs. One patch should therefore not
>> make changes in multiple of those projects otherwise it cannot be
>> merged.
>> Additionally, a check is added to make sure that a series only touches
>> files from one project to avoid having to figure out which patch is to
>> be merged by which maintainer in which project repo.
>>
>> Moreover, it is not uncommon to have people develop patches for those
>> projects from within poky. This wrapper figures out which mailing lists
>> to send patches to based on the files that are modified in the series.
>> Considering that patches to the bitbake/doc/ directory need to be sent
>> to both the bitbake and yocto-docs mailing list, this wrapper handles
>> that. A limitation of the script (lsdiff actually) is that it doesn't
>> know how to handle empty files, but those should be of rather rare
>> occurrences.
>>
>> Note that this script requires hardcoding of paths that are handled by
>> different projects to map files to projects. Anything not mapped is
>> assumed part of OE-Core.
>>
>> [1] https://pypi.org/project/b4/
>> [2] https://b4.docs.kernel.org/en/latest/config.html
>>
>> Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
>> ---
[...]
> I'm not against this but I would like to:
> 
> a) Put the script into OE-Core, just so that we don't have "magic"
> things in poky.

It is very poky-specific though. I don't have anything in mind right now 
but I could imagine we could need a b4 wrapper for openembedded-core at 
some point as well. b4-wrapper-oe-core.py and b4-wrapper-poky.py could 
coexistthough and be pointed at specifically by each repo's .b4-config.

Don't mind it, just find it a bit odd.

I haven't checked if b4 follows symlink, but if it can, would you want 
.b4-config.poky in OE-Core too and then we do the same dance as with the 
README.md in OE-Core/poky?

> b) Perhaps name it something a little more poky specific

b4-wrapper-poky.py? b4-wrapper-poky.py to match README.poky.md for 
example? My brain has already decided it's the weekend so not very 
creative right now :)

Cheers,
Quentin
Richard Purdie Jan. 31, 2025, 2:38 p.m. UTC | #3
On Fri, 2025-01-24 at 19:06 +0100, Quentin Schulz wrote:
> Hi Richard,
> 
> On 1/24/25 6:48 PM, Richard Purdie wrote:
> > On Thu, 2025-01-23 at 16:44 +0100, Quentin Schulz via lists.yoctoproject.org wrote:
> > > From: Quentin Schulz <quentin.schulz@cherry.de>
> > > 
> > > b4[1] is a very nice tool for mail-based contribution. A config[2] file
> > > exists to set up a few defaults. We can use it to set the Cc recipients
> > > to always add, in our case the mailing list.
> > > 
> > > This also adds a wrapper script that is called by b4 to check that each
> > > patch in the series is only for one project. Indeed, poky is actually
> > > a "collection" of multiple repositories, namely BitBake,
> > > OpenEmbedded-Core and the Yocto Docs. One patch should therefore not
> > > make changes in multiple of those projects otherwise it cannot be
> > > merged.
> > > Additionally, a check is added to make sure that a series only touches
> > > files from one project to avoid having to figure out which patch is to
> > > be merged by which maintainer in which project repo.
> > > 
> > > Moreover, it is not uncommon to have people develop patches for those
> > > projects from within poky. This wrapper figures out which mailing lists
> > > to send patches to based on the files that are modified in the series.
> > > Considering that patches to the bitbake/doc/ directory need to be sent
> > > to both the bitbake and yocto-docs mailing list, this wrapper handles
> > > that. A limitation of the script (lsdiff actually) is that it doesn't
> > > know how to handle empty files, but those should be of rather rare
> > > occurrences.
> > > 
> > > Note that this script requires hardcoding of paths that are handled by
> > > different projects to map files to projects. Anything not mapped is
> > > assumed part of OE-Core.
> > > 
> > > [1] https://pypi.org/project/b4/
> > > [2] https://b4.docs.kernel.org/en/latest/config.html
> > > 
> > > Signed-off-by: Quentin Schulz <quentin.schulz@cherry.de>
> > > ---
> [...]
> > I'm not against this but I would like to:
> > 
> > a) Put the script into OE-Core, just so that we don't have "magic"
> > things in poky.
> 
> It is very poky-specific though. I don't have anything in mind right now 
> but I could imagine we could need a b4 wrapper for openembedded-core at 
> some point as well. b4-wrapper-oe-core.py and b4-wrapper-poky.py could 
> coexistthough and be pointed at specifically by each repo's .b4-config.
> 
> Don't mind it, just find it a bit odd.

It is a little unusual but I'd rather this than adding poky specific
content. I'm find with either a poky specific script or a parameter to
put it into "poky" mode.

> I haven't checked if b4 follows symlink, but if it can, would you want 
> .b4-config.poky in OE-Core too and then we do the same dance as with the 
> README.md in OE-Core/poky?

I think that would be confusing, I'm find with two different files and
combo-layer can handle that easily.

> > b) Perhaps name it something a little more poky specific
> 
> b4-wrapper-poky.py? b4-wrapper-poky.py to match README.poky.md for 
> example? My brain has already decided it's the weekend so not very 
> creative right now :)

Something like that. I'm also not feeling very creative atm!

Cheers,

Richard
diff mbox series

Patch

diff --git a/.b4-config b/.b4-config
new file mode 100644
index 0000000000000000000000000000000000000000..26c0dd54e8b1f96bf6d1bf65520c7ec5d72edaa2
--- /dev/null
+++ b/.b4-config
@@ -0,0 +1,3 @@ 
+[b4]
+  prep-perpatch-check-cmd = ./b4-wrapper.py prep-perpatch-check-cmd
+  send-auto-cc-cmd = ./b4-wrapper.py send-auto-cc-cmd
diff --git a/b4-wrapper.py b/b4-wrapper.py
new file mode 100755
index 0000000000000000000000000000000000000000..d64754cb02309a443eb9b7748877e62731b3ecfb
--- /dev/null
+++ b/b4-wrapper.py
@@ -0,0 +1,162 @@ 
+#!/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,
+#
+# 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.
+#
+# This script takes as stdin a patch.
+
+import pathlib
+import re
+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 subprocess.call(["which", "lsdiff"], stdout=subprocess.DEVNULL) != 0:
+    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)
+
+    project_paths = {
+            "bitbake": ["bitbake/*"],
+            "yocto-docs": ["documentation/*"],
+            "poky": [
+                "meta-poky/*",
+                "meta-yocto-bsp/*",
+                "README.hardware.md",
+                "README.poky.md",
+                ".b4-config",
+                "b4-wrapper.py",
+                ],
+    }
+
+    if cmd == "send-auto-cc-cmd":
+        # Patches to BitBake documentation should also go to yocto-docs mailing list
+        project_paths["yocto-docs"] += ["bitbake/doc/*"]
+
+    # List of projects touched by this patch
+    projs = []
+
+    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):
+            projs.append(proj)
+            continue
+
+        # Handle patches made with --no-prefix
+        files = subprocess.check_output(["lsdiff"] + lsdiff_args,
+                                        input=patch, text=True)
+        if len(files):
+            projs.append(proj)
+
+    if len(projs) == 0:
+        # Catch-all for everything not poky-specific or in bitbake/yocto-docs
+        projs.append("openembedded-core")
+
+    if cmd == "prep-perpatch-check-cmd":
+        if len(projs) > 1:
+            print(f"Diff spans more than one project ({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
+        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()