public inbox for openembedded-core@lists.openembedded.org
 help / color / mirror / Atom feed
From: Mark Asselstine <mark.asselstine@windriver.com>
To: Alexander Kanavin <alex.kanavin@gmail.com>,
	Stefan Herbrechtsmeier
	<stefan.herbrechtsmeier-oss@weidmueller.com>
Cc: OE-core <openembedded-core@lists.openembedded.org>,
	Lukas Funke <lukas.funke@weidmueller.com>,
	Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
Subject: Re: [OE-core] [PATCH 4/5] recipetool: add go recipe generator
Date: Wed, 11 May 2022 16:37:04 -0400	[thread overview]
Message-ID: <a4b108ef-12e1-f58b-9fbc-6a83d8d8666d@windriver.com> (raw)
In-Reply-To: <CANNYZj-Ve7+CDyrfdNP8rQbYO-JY+yo=n5E8A3EcHLjAyVSHFQ@mail.gmail.com>



On 2022-05-06 03:15, Alexander Kanavin wrote:
> This is a lot of code. Can you add some documentation for it, what it
> does and how it works? If someone would want to understand it, how
> would they go about it?
> 
> Alex
> 
> On Fri, 6 May 2022 at 09:00, Stefan Herbrechtsmeier
> <stefan.herbrechtsmeier-oss@weidmueller.com> wrote:
>>
>> From: Lukas Funke <lukas.funke@weidmueller.com>
>>
>> Signed-off-by: Lukas Funke <lukas.funke@weidmueller.com>
>> Signed-off-by: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
>> ---
>>
>>   scripts/lib/recipetool/create_go.py | 394 ++++++++++++++++++++++++++++
>>   1 file changed, 394 insertions(+)
>>   create mode 100644 scripts/lib/recipetool/create_go.py
>>
>> diff --git a/scripts/lib/recipetool/create_go.py b/scripts/lib/recipetool/create_go.py
>> new file mode 100644
>> index 0000000000..4552e9b470
>> --- /dev/null
>> +++ b/scripts/lib/recipetool/create_go.py
>> @@ -0,0 +1,394 @@
>> +# Recipe creation tool - go support plugin
>> +#
>> +# Copyright (C) 2022 Weidmueller GmbH & Co KG
>> +# Author: Lukas Funke <lukas.funke@weidmueller.com>
>> +#
>> +# Copyright (c) 2009 The Go Authors. All rights reserved.
>> +#
>> +#  SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-only
>> +#
>> +import bb.utils
>> +from collections import namedtuple
>> +from enum import Enum
>> +from html.parser import HTMLParser
>> +import json
>> +import logging
>> +import os
>> +import re
>> +import subprocess
>> +import sys
>> +import tempfile
>> +import shutil
>> +from urllib.error import URLError, HTTPError
>> +import urllib.parse
>> +import urllib.request
>> +
>> +from recipetool.create import RecipeHandler, handle_license_vars, ensure_native_cmd
>> +
>> +GoImport = namedtuple('GoImport', 'reporoot vcs repourl suffix')
>> +logger = logging.getLogger('recipetool')
>> +
>> +tinfoil = None
>> +
>> +re_pseudo_semver = re.compile(r"v([0-9]+)\.([0-9]+).([0-9]+|\([0-9]+\+1\))-(pre\.[0-9]+\.)?([0-9]+\.)?(?P<utc>[0-9]+)-(?P<sha1_abbrev>[0-9Aa-zA-Z]+)")
>> +re_semver = re.compile(r"^v(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$")
>> +
>> +def tinfoil_init(instance):
>> +    global tinfoil
>> +    tinfoil = instance
>> +
>> +class GoRecipeHandler(RecipeHandler):
>> +
>> +    def _resolve_repository_static(self, modulepath):
>> +        _rootpath = None
>> +        _vcs = None
>> +        _repourl = None
>> +        _suffix = None
>> +
>> +        host, _, path = modulepath.partition('/')
>> +
>> +        class vcs(Enum):
>> +            pathprefix = "pathprefix"
>> +            regexp = "regexp"
>> +            vcs = "vcs"
>> +            repo = "repo"
>> +            check = "check"
>> +            schemelessRepo = "schemelessRepo"
>> +
>> +        # GitHub
>> +        vcsGitHub = {}
>> +        vcsGitHub[vcs.pathprefix] = "github.com"
>> +        vcsGitHub[vcs.regexp] = re.compile(r'^(?P<root>github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/(?P<suffix>[A-Za-z0-9_.\-]+))*$')
>> +        vcsGitHub[vcs.vcs] = "git"
>> +        vcsGitHub[vcs.repo] = "https://\g<root>"
>> +
>> +        # Bitbucket
>> +        vcsBitbucket = {}
>> +        vcsBitbucket[vcs.pathprefix] = "bitbucket.org"
>> +        vcsBitbucket[vcs.regexp] = re.compile(r'^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/(?P<suffix>[A-Za-z0-9_.\-]+))*$')
>> +        vcsBitbucket[vcs.vcs] = "git"
>> +        vcsBitbucket[vcs.repo] = "https://\g<root>"
>> +
>> +        # IBM DevOps Services (JazzHub)
>> +        vcsIBMDevOps = {}
>> +        vcsIBMDevOps[vcs.pathprefix] = "hub.jazz.net/git"
>> +        vcsIBMDevOps[vcs.regexp] = re.compile(r'^(?P<root>hub\.jazz\.net/git/[a-z0-9]+/[A-Za-z0-9_.\-]+)(/(?P<suffix>[A-Za-z0-9_.\-]+))*$')
>> +        vcsIBMDevOps[vcs.vcs] = "git"
>> +        vcsIBMDevOps[vcs.repo] = "https://\g<root>"
>> +
>> +        # Git at Apache
>> +        vcsApacheGit = {}
>> +        vcsApacheGit[vcs.pathprefix] = "git.apache.org"
>> +        vcsApacheGit[vcs.regexp] = re.compile(r'^(?P<root>git\.apache\.org/[a-z0-9_.\-]+\.git)(/(?P<suffix>[A-Za-z0-9_.\-]+))*$')
>> +        vcsApacheGit[vcs.vcs] = "git"
>> +        vcsApacheGit[vcs.repo] = "https://\g<root>"
>> +
>> +        # Git at OpenStack
>> +        vcsOpenStackGit = {}
>> +        vcsOpenStackGit[vcs.pathprefix] = "git.openstack.org"
>> +        vcsOpenStackGit[vcs.regexp] = re.compile(r'^(?P<root>git\.openstack\.org/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(\.git)?(/(?P<suffix>[A-Za-z0-9_.\-]+))*$')
>> +        vcsOpenStackGit[vcs.vcs] = "git"
>> +        vcsOpenStackGit[vcs.repo] = "https://\g<root>"
>> +
>> +        # chiselapp.com for fossil
>> +        vcsChiselapp = {}
>> +        vcsChiselapp[vcs.pathprefix] = "chiselapp.com"
>> +        vcsChiselapp[vcs.regexp] = re.compile(r'^(?P<root>chiselapp\.com/user/[A-Za-z0-9]+/repository/[A-Za-z0-9_.\-]+)$')
>> +        vcsChiselapp[vcs.vcs] = "fossil"
>> +        vcsChiselapp[vcs.repo] = "https://\g<root>"
>> +
>> +        # General syntax for any server.
>> +        # Must be last.
>> +        vcsGeneralServer = {}
>> +        vcsGeneralServer[vcs.regexp] = re.compile("(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?(/~?[A-Za-z0-9_.\-]+)+?)\.(?P<vcs>bzr|fossil|git|hg|svn))(/~?(?P<suffix>[A-Za-z0-9_.\-]+))*$")
>> +        vcsGeneralServer[vcs.schemelessRepo] = True
>> +
>> +        vcsPaths = [vcsGitHub, vcsBitbucket, vcsIBMDevOps, vcsApacheGit, vcsOpenStackGit, vcsChiselapp, vcsGeneralServer]
>> +
>> +        if modulepath.startswith("example.net") or modulepath == "rsc.io":
>> +            logger.warning("Suspicious module path %s" % modulepath)
>> +            return None
>> +        if modulepath.startswith("http:") or modulepath.startswith("https:"):
>> +            logger.warning("Import path should not start with %s %s" % ("http", "https"))
>> +            return None
>> +
>> +        for srv in vcsPaths:
>> +            m = srv[vcs.regexp].match(modulepath)
>> +            if vcs.pathprefix in srv:
>> +                if host == srv[vcs.pathprefix]:
>> +                    _rootpath = m.group('root')
>> +                    _vcs = srv[vcs.vcs]
>> +                    _repourl = m.expand(srv[vcs.repo])
>> +                    _suffix = m.group('suffix')
>> +                    break
>> +            elif m and srv[vcs.schemelessRepo]:
>> +                _rootpath = m.group('root')
>> +                _vcs = m[vcs.vcs]
>> +                _repourl = m[vcs.repo]
>> +                _suffix = m.group('suffix')
>> +                break
>> +
>> +        return GoImport(_rootpath, _vcs, _repourl, _suffix)
>> +
>> +    def _resolve_repository_dynamic(self, modulepath):
>> +
>> +        url = urllib.parse.urlparse("https://" + modulepath)
>> +
>> +        class GoImportHTMLParser(HTMLParser):
>> +
>> +            def __init__(self):
>> +                super().__init__()
>> +                self.__srv = []
>> +
>> +            def handle_starttag(self, tag, attrs):
>> +                if tag == 'meta' and list(filter(lambda a: (a[0] == 'name' and a[1] == 'go-import'), attrs)):
>> +                    content = list(filter(lambda a: (a[0] == 'content'), attrs))
>> +                    if content:
>> +                        self.__srv = content[0][1].split()
>> +
>> +            @property
>> +            def rootpath(self):
>> +                return self.__srv[0]
>> +
>> +            @property
>> +            def vcs(self):
>> +                return self.__srv[1]
>> +
>> +            @property
>> +            def repourl(self):
>> +                return self.__srv[2]
>> +
>> +        req = urllib.request.Request(url.geturl() + "?go-get=1")
>> +
>> +        try:
>> +            resp = urllib.request.urlopen(req)
>> +        except URLError as url_err:
>> +            logger.error("Error while fetching redirect page: %s", str(url_err))
>> +            return None
>> +        except HTTPError as http_err:
>> +            logger.error("Error while fetching redirect page: %s", str(http_err))
>> +            return None
>> +
>> +        parser = GoImportHTMLParser()
>> +        parser.feed(resp.read().decode('utf-8'))
>> +        parser.close()
>> +
>> +        return GoImport(parser.rootpath, parser.vcs, parser.repourl, None)

Attempting this on minio client

recipetool create -o ./maa https://github.com/minio/mc \
   -S b5a0640899f8f8653bcacd19791c92ca22066ba3

I am seeing

   File "/home/mark/git/poky/scripts/lib/recipetool/create_go.py", line 
151, in rootpath
     return self.__srv[0]
IndexError: list index out of range

Is more error checking required?

MarkA

>> +
>> +    def _resolve_repository(self, modulepath):
>> +        """
>> +        Resolves src uri from go module-path
>> +        """
>> +        repodata = self._resolve_repository_static(modulepath)
>> +        if not repodata.repourl:
>> +            repodata = self._resolve_repository_dynamic(modulepath)
>> +
>> +        if repodata:
>> +            logger.info("Resolved download path for import '%s' => %s", modulepath, repodata.repourl)
>> +
>> +        return repodata
>> +
>> +    def _resolve_pseudo_semver(self, d, repo, module_version):
>> +        hash = None
>> +
>> +        def vcs_fetch_all():
>> +            tmpdir = tempfile.mkdtemp()
>> +            clone_cmd = "%s clone --bare %s %s" % ('git', repo, tmpdir)
>> +            bb.process.run(clone_cmd)
>> +            log_cmd = "git log --all --pretty='%H %d' --decorate=short"
>> +            output, errors = bb.process.run(log_cmd, shell=True, stderr=subprocess.PIPE, cwd=tmpdir)
>> +            bb.utils.prunedir(tmpdir)
>> +            return output.strip().split('\n')
>> +
>> +        def vcs_fetch_remote(search=""):
>> +            ls_remote_cmd = "git ls-remote --tags {} {}".format(repo, search)
>> +            output, errors = bb.process.run(ls_remote_cmd)
>> +            return output.strip().split('\n')
>> +
>> +        m_pseudo_semver = re_pseudo_semver.match(module_version)
>> +        if m_pseudo_semver:
>> +            remote_refs = vcs_fetch_all()
>> +            short_commit = m_pseudo_semver.group('sha1_abbrev')
>> +            for l in remote_refs:
>> +                r = l.split(maxsplit=1)
>> +                sha1 = r[0] if len(r) else None
>> +                if not sha1:
>> +                    logger.error("Ups: could not resolve abbref commit for %s" % short_commit)
>> +
>> +                elif sha1.startswith(short_commit):
>> +                    hash = sha1
>> +                    break
>> +        else:
>> +            m_semver = re_semver.match(module_version)
>> +            if m_semver:
>> +
>> +                def get_sha1_remote(re, groupId):
>> +                    for l in remote_refs:
>> +                        r = l.split(maxsplit=1)
>> +                        sha1 = r[0] if len(r) else None
>> +                        ref = r[1] if len(r) == 2 else None
>> +                        if ref:
>> +                            m = re.match(ref)
>> +                            if m and semver_tag in m.group(groupId).split(','):
>> +                                return sha1
>> +                    return None
>> +
>> +                re_tags_remote = re.compile(r"refs/tags/(?P<tag>[0-9A-Za-z-_\.]+)")
>> +                re_tags_all = re.compile(r"\((HEAD -> (.*), )?tag: *((?:([0-9A-Za-z-_\.]+),? *)+)\)")
>> +                semver_tag = "v" + m_semver.group('major') + "."\
>> +                                +m_semver.group('minor') + "."\
>> +                                +m_semver.group('patch') \
>> +                                +(("-" + m_semver.group('prerelease')) if m_semver.group('prerelease') else "")
>> +                remote_refs = vcs_fetch_remote(semver_tag)
>> +                # probe tag using 'ls-remote', which is faster than fetching complete history
>> +                sha1 = get_sha1_remote(re_tags_remote, 'tag')
>> +                if sha1:
>> +                    hash = sha1
>> +                else:
>> +                    # backup: fetch complete history
>> +                    remote_refs = vcs_fetch_all()
>> +                    hash = get_sha1_remote(re_tags_all, 3)
>> +        return hash
>> +
>> +    def _handle_dependencies(self, d, srctree, go_mod):
>> +        runenv = dict(os.environ, PATH=d.getVar('PATH'))
>> +        src_uris = []
>> +        src_revs = []
>> +        for require in go_mod['Require']:
>> +            module_path = require['Path']
>> +            module_version = require['Version']
>> +
>> +            repodata = self._resolve_repository(module_path)
>> +            commit_id = self._resolve_pseudo_semver(d, repodata.repourl, module_version)
>> +            url = urllib.parse.urlparse(repodata.repourl)
>> +            repo_url = url.netloc + url.path
>> +            inline_fcn = "${@go_src_uri("
>> +            inline_fcn += "'{}'".format(repo_url)
>> +            if repo_url != module_path:
>> +                inline_fcn += ",path='{}'".format(module_path)
>> +            if repodata.suffix and not re.match("v[0-9]+", repodata.suffix):
>> +                inline_fcn += ",subdir='{}'".format(repodata.suffix)
>> +            if repodata.vcs != 'git':
>> +                inline_fcn += ",vcs='{}'".format(repodata.vcs)
>> +            inline_fcn += ")}"
>> +
>> +            src_uris.append(inline_fcn)
>> +            flat_module_path = module_path.replace('/', '.')
>> +            src_rev = "# %s@%s => %s\n" % (module_path, module_version, commit_id)
>> +            src_rev += "SRCREV_%s = \"%s\"\n" % (flat_module_path, commit_id)
>> +            src_rev += "GO_MODULE_PATH[%s] = \"%s\"\n" % (flat_module_path, module_path)
>> +            src_rev += "GO_MODULE_VERSION[%s] = \"%s\"" % (flat_module_path, module_version)
>> +            src_revs.append(src_rev)
>> +
>> +        return src_uris, src_revs
>> +
>> +    def _go_mod_patch(self, patchfile, go_import, srctree, localfilesdir, extravalues, d):
>> +        runenv = dict(os.environ, PATH=d.getVar('PATH'))
>> +        # first remove go.mod and go.sum, otherwise 'go mod init' will fail
>> +        bb.utils.remove(os.path.join(srctree, "go.mod"))
>> +        bb.utils.remove(os.path.join(srctree, "go.sum"))
>> +        bb.process.run("go mod init %s" % go_import, stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        bb.process.run("go mod tidy", stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        output, _ = bb.process.run("go mod edit -json", stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        bb.process.run("git diff go.mod > %s" % (patchfile), stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        bb.process.run("git checkout HEAD go.mod go.sum;", stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        go_mod = json.loads(output)
>> +        tmpfile = os.path.join(localfilesdir, patchfile)
>> +        shutil.move(os.path.join(srctree, patchfile), tmpfile)
>> +        extravalues.setdefault('extrafiles', {})
>> +        extravalues['extrafiles'][patchfile] = tmpfile
>> +
>> +        return go_mod
>> +
>> +    def process(self, srctree, classes, lines_before, lines_after, handled, extravalues):
>> +
>> +        if 'buildsystem' in handled:
>> +            return False
>> +
>> +        files = RecipeHandler.checkfiles(srctree, ['go.mod'])
>> +        if not files:
>> +            return False
>> +
>> +        go_bindir = ensure_native_cmd(tinfoil, "go")
>> +
>> +        d = bb.data.createCopy(tinfoil.config_data)
>> +        d.prependVar('PATH', '%s:' % go_bindir)
>> +        handled.append('buildsystem')
>> +        classes.append("go-vendor")
>> +
>> +        runenv = dict(os.environ, PATH=d.getVar('PATH'))
>> +        output, _ = bb.process.run("go mod edit -json", stderr=subprocess.STDOUT, env=runenv, shell=True, cwd=srctree)
>> +        go_mod = json.loads(output)
>> +
>> +        go_import = go_mod['Module']['Path']
>> +        go_version_match = re.match("([0-9]+).([0-9]+)", go_mod['Go'])
>> +        go_version_major = int(go_version_match.group(1))
>> +        go_version_minor = int(go_version_match.group(2))
>> +        src_uris = []
>> +        if go_version_major == 1 and go_version_minor < 17:
>> +            logger.warning("go.mod files generated by Go < 1.17 might have incomplete indirect dependencies.")
>> +            patchfile = "go.mod.patch"
>> +            localfilesdir = tempfile.mkdtemp(prefix='recipetool-go-')
>> +            go_mod = self._go_mod_patch(patchfile, go_import, srctree, localfilesdir, extravalues, d)
>> +            src_uris.append("file://%s;patchdir=src/${GO_IMPORT}" % (patchfile))
>> +
>> +        if not os.path.exists(os.path.join(srctree, "vendor")):
>> +            dep_src_uris, src_revs = self._handle_dependencies(d, srctree, go_mod)
>> +            src_uris.extend(dep_src_uris)
>> +            lines_after.append("#TODO: Subdirectories are heuristically derived from " \
>> +                              "the import path and might be incorrect.")
>> +            for src_rev in src_revs:
>> +                lines_after.append(src_rev)
>> +
>> +        self._rewrite_src_uri(src_uris, lines_before)
>> +
>> +        handle_license_vars(srctree, lines_before, handled, extravalues, d)
>> +        self._rewrite_lic_uri(lines_before)
>> +
>> +        lines_before.append("GO_IMPORT = \"{}\"".format(go_import))
>> +        lines_before.append("SRCREV_FORMAT = \"${PN}\"")
>> +
>> +    def _update_lines_before(self, updated, newlines, lines_before):
>> +        if updated:
>> +            del lines_before[:]
>> +            for line in newlines:
>> +                # Hack to avoid newlines that edit_metadata inserts
>> +                if line.endswith('\n'):
>> +                    line = line[:-1]
>> +                lines_before.append(line)
>> +        return updated
>> +
>> +    def _rewrite_lic_uri(self, lines_before):
>> +
>> +        def varfunc(varname, origvalue, op, newlines):
>> +            if varname == 'LIC_FILES_CHKSUM':
>> +                new_licenses = []
>> +                licenses = origvalue.split()
>> +
>> +                for license in licenses:
>> +                    uri, chksum = license.split(';', 1)
>> +                    url = urllib.parse.urlparse(uri)
>> +                    new_uri = os.path.join(url.scheme + "://", "src", "${GO_IMPORT}", url.netloc + url.path) + ";" + chksum
>> +                    new_licenses.append(new_uri)
>> +
>> +                return new_licenses, None, -1, True
>> +            return origvalue, None, 0, True
>> +
>> +        updated, newlines = bb.utils.edit_metadata(lines_before, ['LIC_FILES_CHKSUM'], varfunc)
>> +        return self._update_lines_before(updated, newlines, lines_before)
>> +
>> +    def _rewrite_src_uri(self, src_uris_deps, lines_before):
>> +
>> +        def varfunc(varname, origvalue, op, newlines):
>> +            if varname == 'SRC_URI':
>> +                src_uri = []
>> +                src_uri.append("git://${GO_IMPORT};nobranch=1;name=${PN};protocol=https")
>> +                src_uri.extend(src_uris_deps)
>> +                return src_uri, None, -1, True
>> +            return origvalue, None, 0, True
>> +
>> +        updated, newlines = bb.utils.edit_metadata(lines_before, ['SRC_URI'], varfunc)
>> +        return self._update_lines_before(updated, newlines, lines_before)
>> +
>> +def register_recipe_handlers(handlers):
>> +    handlers.append((GoRecipeHandler(), 60))
>> --
>> 2.30.2
>>
>>
>>
>>
>>
>>
>> -=-=-=-=-=-=-=-=-=-=-=-
>> Links: You receive all messages sent to this group.
>> View/Reply Online (#165331): https://lists.openembedded.org/g/openembedded-core/message/165331
>> Mute This Topic: https://lists.openembedded.org/mt/90928688/3616946
>> Group Owner: openembedded-core+owner@lists.openembedded.org
>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [mark.asselstine@windriver.com]
>> -=-=-=-=-=-=-=-=-=-=-=-
>>


  reply	other threads:[~2022-05-11 20:37 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-06  6:59 [PATCH 1/5] recipetool-create: add ensure_native_cmd function Stefan Herbrechtsmeier
2022-05-06  6:59 ` [PATCH 2/5] create_npm: reuse ensure_native_cmd from create.py Stefan Herbrechtsmeier
2022-05-06  6:59 ` [PATCH 3/5] poky-meta: add go vendor class for offline builds Stefan Herbrechtsmeier
2022-05-11 20:42   ` [OE-core] " Mark Asselstine
2022-05-06  6:59 ` [PATCH 4/5] recipetool: add go recipe generator Stefan Herbrechtsmeier
2022-05-06  7:15   ` [OE-core] " Alexander Kanavin
2022-05-11 20:37     ` Mark Asselstine [this message]
2022-05-06  6:59 ` [PATCH 5/5] oe-selftest: add go recipe create selftest Stefan Herbrechtsmeier
2022-05-06  7:16   ` [OE-core] " Alexander Kanavin
2022-05-11 20:08     ` Mark Asselstine
2022-05-06  7:09 ` [OE-core] [PATCH 1/5] recipetool-create: add ensure_native_cmd function Alexander Kanavin
2022-05-11 19:45   ` Mark Asselstine
2022-05-11 19:47     ` Mark Asselstine
2022-05-09 21:46 ` Luca Ceresoli
2022-05-11  6:54   ` Stefan Herbrechtsmeier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=a4b108ef-12e1-f58b-9fbc-6a83d8d8666d@windriver.com \
    --to=mark.asselstine@windriver.com \
    --cc=alex.kanavin@gmail.com \
    --cc=lukas.funke@weidmueller.com \
    --cc=openembedded-core@lists.openembedded.org \
    --cc=stefan.herbrechtsmeier-oss@weidmueller.com \
    --cc=stefan.herbrechtsmeier@weidmueller.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox