Chen Qi via lists.yoctoproject.org <Qi.Chen= windriver....@lists.yoctoproject.org> escreveu no dia terça, 5/09/2023 à(s) 04:22:
> From: Chen Qi <qi.c...@windriver.com> > > oe-go-mod-autogen.py is a helper script for go mod recipes. It follows > Bruce's initiative about how to deal with go mod recipes in OE. > Place the below description on some README file or commented itself inside the script can be useful for others. Jose Example: > cmd: <path_to>/meta-virtualization/scripts/oe-go-mod-autogen.py \ > --repo https://github.com/docker/compose --rev v2.20.3 > output: src_uri.inc, relocation.inc, modules.txt > > Copy these three generated files to replace the original ones, > then we only need update PV and SRCREV, and docker-compose is upgraded. > > Below are some technical details. > > * get module's repo from module name > > This script checks the following two URLs to determine the module's repo. > 1. https://<module_name_tweaked>?=go-get=1 > 2. https://pkg.go.dev/<module_name_tweaked> > > The module_name_tweaked is derived from module_name, with the last > components > removed one by one. Let me use two examples to explain this. > > For module_name sigs.k8s.io/json, the sigs.k8s.io/json is first used as > module_name_tweaked for searching. And we can correctly get the repo > URL, so > the search stops. > > For module_name github.com/k3s-io/etcd/api/v3, the following ones are > used > as module_name_tweaked: > github.com/k3s-io/etcd/api/v3 > github.com/k3s-io/etcd/api > github.com/k3s-io/etcd > And when searching 'github.com/k3s-io/etcd', we get the repo URL, so > the search > stops. > > * determine the srcdir:destdir mapping in 'vendor' creation > > To correctly form the 'vendor' directory, the mapping is critical. > This script makes use of tag matching and path matching to determine > the subpath in the repo for the module. > > * avoid subpath being overriden by parent path > > We need to avoid subpath being overriden by parent path. This is needed > for both SRC_URI ordering in src_uri.inc and the sites mapping ordering > in relocation.inc. This script simply uses the length as the ordering > key, > simply for the reason that if a path is a subpath of another path, it > must > be longer. > > * the .git suffix is removed to sync with each other > > Unlike normal recipes, go mod recipe usually have many SRC_URIs. This > script > remove the '.git' suffix from repo URL so that the repo URLs are in sync > with each. > > * basic directory hierarchy and caching mechanism > > <cwd>/repos: hold the repos downloaded and checked > <cwd>/wget-contents: hold the contents to determine the module's repo > <cwd>/wget-contents/<module_name>.repo_url.cache: the repo value cache > This is to avoid unnecessary URL fetching and repo cloning. > > * the ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE switch in script > > The script must get the correct repo_url, fullsrc_rev and subpath for > each required module in go.mod to correctly generate the src_uri.inc and > relocation.inc files. If this process fails for any required module, this > script stop immediately, as I deliberately set > ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE > to True in this script. The purpose is to encourage people to report > problems to meta-virt so that we can improve this script according to > these feedbacks. But this variable can set to False, then the script > only records the failed modules in self.modules_unhandled with reasons > added, people can modify the generated src_uri.inc and relocation.inc > to manually handle these unhandled modules if they are urgent to > add/upgrade some go mod recipes. > > Signed-off-by: Chen Qi <qi.c...@windriver.com> > --- > scripts/oe-go-mod-autogen.py | 663 +++++++++++++++++++++++++++++++++++ > 1 file changed, 663 insertions(+) > create mode 100755 scripts/oe-go-mod-autogen.py > > diff --git a/scripts/oe-go-mod-autogen.py b/scripts/oe-go-mod-autogen.py > new file mode 100755 > index 00000000..09d6133b > --- /dev/null > +++ b/scripts/oe-go-mod-autogen.py > @@ -0,0 +1,663 @@ > +#!/usr/bin/env python3 > + > +import os > +import sys > +import logging > +import argparse > +from collections import OrderedDict > +import subprocess > + > +# This switch is used to make this script error out ASAP, mainly for > debugging purpose > +ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE = True > + > +logger = logging.getLogger('oe-go-mod-autogen') > +loggerhandler = logging.StreamHandler() > +loggerhandler.setFormatter(logging.Formatter("%(levelname)s: > %(message)s")) > +logger.addHandler(loggerhandler) > +logger.setLevel(logging.INFO) > + > +class GoModTool(object): > + def __init__(self, repo, rev, workdir): > + self.repo = repo > + self.rev = rev > + self.workdir = workdir > + > + # Stores the actual module name and its related information > + # {module: (repo_url, repo_dest_dir, fullsrcrev)} > + self.modules_repoinfo = {} > + > + # {module_name: (url, version, destdir, fullsrcrev)} > + # > + # url: place to get the source codes, we only support git repo > + # version: module version, git tag or git rev > + # destdir: place to put the fetched source codes > + # fullsrcrev: full src rev which is the value of SRC_REV > + # > + # e.g. > + # For 'github.com/Masterminds/semver/v3 v3.1.1' in go.mod: > + # module_name = github.com/Masterminds/semver/v3 > + # url = https://github.com/Masterminds/semver > + # version = v3.1.1 > + # destdir = ${WORKDIR}/${BP}/src/${GO_IMPORT}/vendor/ > github.com/Masterminds/semver/v3 > + # fullsrcrev = d387ce7889a157b19ad7694dba39a562051f41b0 > + self.modules_require = OrderedDict() > + > + # {orig_module: (actual_module, actual_version)} > + self.modules_replace = OrderedDict() > + > + # Unhandled modules > + self.modules_unhandled = OrderedDict() > + > + # store subpaths used to form srcpath > + # {actual_module_name: subpath} > + self.modules_subpaths = OrderedDict() > + > + # modules's actual source paths, record those that are not the > same with the module itself > + self.modules_srcpaths = OrderedDict() > + > + # store lines, comment removed > + self.require_lines = [] > + self.replace_lines = [] > + > + # fetch repo > + self.fetch_and_checkout_repo(self.repo.split('://')[1], > self.repo, self.rev, checkout=True, get_subpath=False) > + > + def show_go_mod_info(self): > + # Print modules_require, modules_replace and modules_unhandled > + print("modules required:") > + for m in self.modules_require: > + url, version, destdir, fullrev = self.modules_require[m] > + print("%s %s %s %s" % (m, version, url, fullrev)) > + > + print("modules replace:") > + for m in self.modules_replace: > + actual_module, actual_version = self.modules_replace[m] > + print("%s => %s %s" % (m, actual_module, actual_version)) > + > + print("modules unhandled:") > + for m in self.modules_unhandled: > + reason = self.modules_unhandled[m] > + print("%s unhandled: %s" % (m, reason)) > + > + def parse(self): > + # check if this repo needs autogen > + repo_url, repo_dest_dir, repo_fullrev = > self.modules_repoinfo[self.repo.split('://')[1]] > + if os.path.isdir(os.path.join(repo_dest_dir, 'vendor')): > + logger.info("vendor direcotry has already existed for %s, no > need to add other repos" % self.repo) > + return > + go_mod_file = os.path.join(repo_dest_dir, 'go.mod') > + if not os.path.exists(go_mod_file): > + logger.info("go.mod file does not exist for %s, no need to > add otehr repos" % self.repo) > + return > + self.parse_go_mod(go_mod_file) > + self.show_go_mod_info() > + > + def fetch_and_checkout_repo(self, module_name, repo_url, rev, > default_protocol='https://', checkout=False, get_subpath=True): > + """ > + Fetch repo_url to <workdir>/repos/repo_base_name > + """ > + protocol = default_protocol > + if '://' in repo_url: > + repo_url_final = repo_url > + else: > + repo_url_final = default_protocol + repo_url > + logger.debug("fetch and checkout %s %s" % (repo_url_final, rev)) > + repos_dir = os.path.join(self.workdir, 'repos') > + if not os.path.exists(repos_dir): > + os.makedirs(repos_dir) > + repo_basename = repo_url.split('/')[-1].split('.git')[0] > + repo_dest_dir = os.path.join(repos_dir, repo_basename) > + module_last_name = module_name.split('/')[-1] > + git_action = "fetch" > + if os.path.exists(repo_dest_dir): > + if checkout: > + # check if current HEAD is rev > + try: > + headrev = subprocess.check_output('git rev-list -1 > HEAD', shell=True, cwd=repo_dest_dir).decode('utf-8').strip() > + requiredrev = subprocess.check_output('git rev-list > -1 %s 2>/dev/null || git rev-list -1 %s/%s' % (rev, module_last_name, rev), > shell=True, cwd=repo_dest_dir).decode('utf-8').strip() > + if headrev == requiredrev: > + logger.info("%s has already been fetched and > checked out as required, skipping" % repo_url) > + self.modules_repoinfo[module_name] = (repo_url, > repo_dest_dir, requiredrev) > + return > + else: > + logger.info("HEAD of %s is not %s, will do a > clean clone" % (repo_dest_dir, requiredrev)) > + git_action = "clone" > + except: > + logger.info("'git rev-list' in %s failed, will do a > clean clone" % repo_dest_dir) > + git_action = "clone" > + else: > + # determine if the current repo points to the desired > remote repo > + try: > + remote_origin_url = subprocess.check_output('git > config --get remote.origin.url', shell=True, > cwd=repo_dest_dir).decode('utf-8').strip() > + if remote_origin_url.endswith('.git'): > + if not repo_url_final.endswith('.git'): > + remote_origin_url = remote_origin_url[:-4] > + else: > + if repo_url_final.endswith('.git'): > + remote_origin_url = remote_origin_url + '.git' > + if remote_origin_url != repo_url_final: > + logger.info("remote.origin.url for %s is not %s, > will do a clean clone" % (repo_dest_dir, repo_url_final)) > + git_action = "clone" > + except: > + logger.info("'git config --get remote.origin.url' in > %s failed, will do a clean clone" % repo_dest_dir) > + git_action = "clone" > + else: > + # No local repo, clone it. > + git_action = "clone" > + > + if git_action == "clone": > + logger.info("Removing %s" % repo_dest_dir) > + subprocess.check_call('rm -rf %s' % repo_dest_dir, shell=True) > + > + # clone/fetch repo > + try: > + git_cwd = repos_dir if git_action == "clone" else > repo_dest_dir > + logger.info("git %s %s in %s" % (git_action, repo_url_final, > git_cwd)) > + subprocess.check_call('git %s %s >/dev/null 2>&1' % > (git_action, repo_url_final), shell=True, cwd=git_cwd) > + except: > + logger.warning("Failed to %s %s in %s" % (git_action, > repo_url_final, git_cwd)) > + return > + > + def get_requiredrev(get_subpath): > + import re > + # check if rev is a revision or a version > + if len(rev) == 12 and re.match('[0-9a-f]+', rev): > + rev_is_version = False > + else: > + rev_is_version = True > + > + # if rev is not a version, 'git rev-list -1 <rev>' should > just succeed! > + if not rev_is_version: > + try: > + rev_return = subprocess.check_output('git rev-list -1 > %s 2>/dev/null' % rev, shell=True, > cwd=repo_dest_dir).decode('utf-8').strip() > + if get_subpath: > + cmd = 'git branch -M toremove && git checkout -b > check_subpath %s && git branch -D toremove' % rev_return > + subprocess.check_call(cmd, shell=True, > cwd=repo_dest_dir) > + # try to get the subpath for this module > + module_name_parts = module_name.split('/') > + while (len(module_name_parts) > 0): > + subpath = '/'.join(module_name_parts) > + dir_to_check = repo_dest_dir + '/' + > '/'.join(module_name_parts) > + if os.path.isdir(dir_to_check): > + self.modules_subpaths[module_name] = > subpath > + break > + else: > + module_name_parts.pop(0) > + return rev_return > + except: > + logger.warning("Revision (%s) not in repo(%s)" % > (rev, repo_dest_dir)) > + return None > + > + # the following codes deals with case where rev is a version > + # determine the longest match tag, in this way, we can get > the current srcpath to be used in relocation.inc > + # we first get the initial tag, which is formed from > module_name and rev > + module_parts = module_name.split('/') > + if rev.startswith(module_parts[-1] + '.'): > + tag = '/'.join(module_parts[:-1]) + '/' + rev > + last_module_part_replaced = True > + else: > + tag = '/'.join(module_parts) + '/' + rev > + last_module_part_replaced = False > + logger.debug("use %s as the initial tag for %s" % (tag, > module_name)) > + tag_parts = tag.split('/') > + while(len(tag_parts) > 0): > + try: > + rev_return = subprocess.check_output('git rev-list -1 > %s 2>/dev/null' % tag, shell=True, > cwd=repo_dest_dir).decode('utf-8').strip() > + if len(tag_parts) > 1: > + # ensure that the subpath exists > + if get_subpath: > + cmd = 'git branch -M toremove && git checkout > -b check_subpath %s && git branch -D toremove' % rev_return > + subprocess.check_call(cmd, shell=True, > cwd=repo_dest_dir) > + # get subpath for the actual_module_name > + if last_module_part_replaced: > + subpath = '/'.join(tag_parts[:-1]) + '/' > + module_parts[-1] > + if not os.path.isdir(repo_dest_dir + '/' > + subpath): > + subpath = '/'.join(tag_parts[:-1]) > + else: > + subpath = '/'.join(tag_parts[:-1]) > + if not os.path.isdir(repo_dest_dir + '/' + > subpath): > + logger.warning("subpath (%s) derived from > tag matching does not exist in %s" % (subpath, repo_dest_dir)) > + return None > + self.modules_subpaths[module_name] = subpath > + logger.info("modules_subpath[%s] = %s" % > (module_name, subpath)) > + return rev_return > + except: > + tag_parts.pop(0) > + tag = '/'.join(tag_parts) > + logger.warning("No tag matching %s" % rev) > + return None > + > + requiredrev = get_requiredrev(get_subpath) > + if requiredrev: > + logger.info("Got module(%s) requiredrev: %s" % (module_name, > requiredrev)) > + if checkout: > + subprocess.check_call('git checkout -b gomodautogen %s' % > requiredrev, shell=True, cwd=repo_dest_dir) > + self.modules_repoinfo[module_name] = (repo_url, > repo_dest_dir, requiredrev) > + else: > + logger.warning("Failed to get requiredrev, repo_url = %s, rev > = %s, module_name = %s" % (repo_url, rev, module_name)) > + > + def parse_go_mod(self, go_mod_path): > + """ > + Parse go.mod file to get the modules info > + """ > + # First we get the require and replace lines > + # The parsing logic assumes the replace lines come *after* the > require lines > + inrequire = False > + inreplace = False > + with open(go_mod_path, 'r') as f: > + lines = f.readlines() > + for line in lines: > + if line.startswith('require ('): > + inrequire = True > + continue > + if line.startswith(')'): > + inrequire = False > + continue > + if line.startswith('require ') or inrequire: > + # we have one line require > + require_line = line.lstrip('require > ').split('//')[0].strip() > + if require_line: > + self.require_lines.append(require_line) > + continue > + # we can deal with requires and replaces separately > because go.mod always writes requires before replaces > + if line.startswith('replace ('): > + inreplace = True > + continue > + if line.startswith(')'): > + inreplace = False > + continue > + if line.startswith('replace ') or inreplace: > + replace_line = line.lstrip('replace > ').split('//')[0].strip() > + if replace_line: > + self.replace_lines.append(replace_line) > + continue > + # > + # parse the require_lines and replace_lines to form > self.modules_require and self.modules_replace > + # > + logger.debug("Parsing require_lines and replace_lines ...") > + # A typical replace line is as below: > + # github.com/hashicorp/golang-lru => > github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c > + # It means that the github.com/hashicorp/golang-lru module is > replaced by github.com/ktock/golang-lru > + # with the version 'v0.5.5-0.20211029085301-ec551be6f75c'. > + # So the destdir is vendor/github.com/hashicorp/golang-lru while > the contents are from github.com/ktock/golang-lru > + for line in self.replace_lines: > + orig_module, actual = line.split('=>') > + actual_module, actual_version = actual.split() > + orig_module = orig_module.strip() > + actual_module = actual_module.strip() > + actual_version = actual_version.strip() > + self.modules_replace[orig_module] = (actual_module, > actual_version) > + # > + # Typical require lines are as below: > + # github.com/Masterminds/semver/v3 v3.1.1 > + # golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 > + # > + # We need to first try https://<module_name>?=go-get=1 to see it > contains > + # line starting with '<meta name="go-import" content='. > + # > + # If so, get root-path vcs repo-url from content. See > https://go.dev/ref/mod#vcs-find > + # For example, the above 'wget > https://golang.org/x/crypto?go-get=1' gives you > + # <meta name="go-import" content="golang.org/x/crypto git > https://go.googlesource.com/crypto"> > + # In such case, the self.modules_require has the following > contents: > + # module_name: golang.org/x/crypto > + # url: https://go.googlesource.com/crypto > + # version: v0.0.0-20220321153916-2c7772ba3064 > + # destdir: ${WORKDIR}/${BP}/src/import/vendor.fetch/ > golang.org/x/crypto > + # fullsrcrev: 2c7772ba30643b7a2026cbea938420dce7c6384d (git > rev-list -1 2c7772ba3064) > + # > + # If not, try https://pkg.go.dev/<module_name>, and find the > 'Repository'. > + # For example, 'wget > https://pkg.go.dev/github.com/Masterminds/semver/v3' gives: > + # github.com/Masterminds/semver > + # In such case, the self.modules has the following contents: > + # module_name: github.com/Masterminds/semver/v3 > + # url: https://github.com/Masterminds/semver > + # version: v3.1.1 > + # destdir: ${WORKDIR}/${BP}/src/import/vendor.fetch/ > github.com/Masterminds/semver/v3 > + # fullsrcrev: 7bb0c843b53d6ad21a3f619cb22c4b442bb3ef3e (git > rev-list -1 v3.1.1) > + # > + # As a last resort, if the last component of <module_name> > matches 'v[0-9]+', > + # remove the last component and try wget https:// > <module_name_with_last_component_removed>?go-get=1, > + # then try using the above matching method. > + # > + for line in self.require_lines: > + module_name, version = line.strip().split() > + logger.debug("require line: %s" % line) > + logger.debug("module_name = %s; version = %s" % (module_name, > version)) > + # take the modules_replace into consideration to get the > actual version and actual module name > + # note that the module_name is used in destdir, and the > actual_module_name and actual_version > + # are used to determine the url and fullsrcrev > + destdir = '${WORKDIR}/${BP}/src/import/vendor.fetch/%s' % > module_name > + actual_module_name = module_name > + actual_version = version > + if module_name in self.modules_replace: > + actual_module_name, actual_version = > self.modules_replace[module_name] > + logger.debug("actual_module_name = %s; actual_version = %s" % > (actual_module_name, actual_version)) > + url, fullsrcrev = self.get_url_srcrev(actual_module_name, > actual_version) > + logger.debug("url = %s; fullsrcrev = %s" % (url, fullsrcrev)) > + if url and fullsrcrev: > + self.modules_require[module_name] = (url, version, > destdir, fullsrcrev) > + # form srcpath, actual_module_name/<subpath> > + if actual_module_name in self.modules_subpaths: > + subpath = self.modules_subpaths[actual_module_name] > + srcpath = '%s/%s' % (actual_module_name, subpath) > + self.modules_srcpaths[module_name] = srcpath > + logger.info("self.modules_srcpaths[%s] = %s" % > (module_name, srcpath)) > + else: > + self.modules_srcpaths[module_name] = > actual_module_name > + else: > + logger.warning("get_url_srcrev(%s, %s) failed" % > (actual_module_name, actual_version)) > + if ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE: > + sys.exit(1) > + > + def use_wget_to_get_repo_url(self, wget_content_file, url_cache_file, > module_name): > + """ > + Use wget to get repo_url for module_name, return None if not found > + """ > + try: > + logger.info("wget -O %s https://%s?=go-get=1" % > (wget_content_file, module_name)) > + subprocess.check_call('wget -O %s https://%s?=go-get=1' % > (wget_content_file, module_name), shell=True) > + with open(wget_content_file, 'r') as f: > + for line in f.readlines(): > + if '<meta name="go-import" content=' in line: > + logger.info("Succeed to find go-import content > for %s" % module_name) > + logger.debug("The line is %s" % line) > + root_path, vcs, repo_url = > line.split('content=')[1].split('"')[1].split() > + logger.info("%s: %s %s %s" % (module_name, > root_path, vcs, repo_url)) > + if vcs != 'git': > + logger.warning('%s unhandled as its vcs is %s > which is not supported by this script.' % (module_name, vcs)) > + unhandled_reason = 'vcs %s is not supported > by this script' % vcs > + self.modules_unhandled[module_name] = > unhandled_reason > + return None > + with open(url_cache_file, 'w') as f: > + f.write(repo_url) > + return repo_url > + except: > + logger.info("wget -O %s https://%s?=go-get=1 failed" % > (wget_content_file, module_name)) > + # if we cannot find repo url from https://<module_name>?=go-get=1, > try https://pkg.go/dev/<module_name> > + try: > + logger.info("wget -O %s https://pkg.go.dev/%s" % > (wget_content_file, module_name)) > + subprocess.check_call("wget -O %s https://pkg.go.dev/%s" % > (wget_content_file, module_name), shell=True) > + repo_url_found = False > + with open(wget_content_file, 'r') as f: > + in_repo_section = False > + for line in f.readlines(): > + if '>Repository<' in line: > + in_repo_section = True > + continue > + if in_repo_section: > + newline = line.strip() > + if newline != '' and not newline.startswith('<'): > + repo_url = newline > + repo_url_found = True > + break > + if repo_url_found: > + logger.info("repo url for %s: %s" % (module_name, > repo_url)) > + with open(url_cache_file, 'w') as f: > + f.write(repo_url) > + return repo_url > + else: > + unhandled_reason = 'cannot determine repo_url for %s' % > module_name > + self.modules_unhandled[module_name] = unhandled_reason > + return None > + except: > + logger.info("wget -O %s https://pkg.go.dev/%s failed" % > (wget_content_file, module_name)) > + return None > + > + > + def get_repo_url_rev(self, module_name, version): > + """ > + Return (repo_url, rev) > + """ > + import re > + # First get rev from version > + v = version.split('+incompatible')[0] > + version_components = v.split('-') > + if len(version_components) == 1: > + rev = v > + elif len(version_components) == 3: > + if len(version_components[2]) == 12: > + rev = version_components[2] > + else: > + rev = v > + else: > + rev = v > + > + # > + # Get repo_url > + # We put a cache mechanism here, > <wget_content_file>.repo_url.cache is used to store the repo url fetch > before > + # > + wget_dir = os.path.join(self.workdir, 'wget-contents') > + if not os.path.exists(wget_dir): > + os.makedirs(wget_dir) > + wget_content_file = os.path.join(wget_dir, > module_name.replace('/', '_')) > + url_cache_file = "%s.repo_url.cache" % wget_content_file > + if os.path.exists(url_cache_file): > + with open(url_cache_file, 'r') as f: > + repo_url = f.readline().strip() > + return (repo_url, rev) > + module_name_parts = module_name.split('/') > + while (len(module_name_parts) > 0): > + module_name_to_check = '/'.join(module_name_parts) > + logger.info("module_name_to_check: %s" % > module_name_to_check) > + repo_url = self.use_wget_to_get_repo_url(wget_content_file, > url_cache_file, module_name_to_check) > + if repo_url: > + return (repo_url, rev) > + else: > + if module_name in self.modules_unhandled: > + return (None, rev) > + else: > + module_name_parts.pop(-1) > + > + unhandled_reason = 'cannot determine the repo for %s' % > module_name > + self.modules_unhandled[module_name] = unhandled_reason > + return (None, rev) > + > + def get_url_srcrev(self, module_name, version): > + """ > + Return url and fullsrcrev according to module_name and version > + """ > + repo_url, rev = self.get_repo_url_rev(module_name, version) > + if not repo_url or not rev: > + return (None, None) > + self.fetch_and_checkout_repo(module_name, repo_url, rev) > + if module_name in self.modules_repoinfo: > + repo_url, repo_dest_dir, repo_fullrev = > self.modules_repoinfo[module_name] > + # remove the .git suffix to sync repos across modules with > different versions and across recipes > + if repo_url.endswith('.git'): > + repo_url = repo_url[:-len('.git')] > + return (repo_url, repo_fullrev) > + else: > + unhandled_reason = 'fetch_and_checkout_repo(%s, %s, %s) > failed' % (module_name, repo_url, rev) > + self.modules_unhandled[module_name] = unhandled_reason > + return (None, None) > + > + def gen_src_uri_inc(self): > + """ > + Generate src_uri.inc file containing SRC_URIs > + """ > + src_uri_inc_file = os.path.join(self.workdir, 'src_uri.inc') > + # record the <name> after writting SRCREV_<name>, this is to > avoid modules having the same basename resulting in same SRCREV_xxx > + srcrev_name_recorded = [] > + template = """# %s %s > +# [1] git ls-remote %s %s > +SRCREV_%s="%s" > +SRC_URI += > "git://%s;name=%s;protocol=https;nobranch=1;destsuffix=${WORKDIR}/${BP}/src/import/vendor.fetch/%s" > + > +""" > + # We can't simply write SRC_URIs one by one in the order that > go.mod specify them. > + # Because the latter one might clean things up for the former one > if the former one is a subpath of the latter one. > + def take_first_len(elem): > + return len(elem[0]) > + > + src_uri_contents = [] > + with open(src_uri_inc_file, 'w') as f: > + for module in self.modules_require: > + # {module_name: (url, version, destdir, fullsrcrev)} > + repo_url, version, destdir, fullrev = > self.modules_require[module] > + if module in self.modules_replace: > + actual_module_name, actual_version = > self.modules_replace[module] > + else: > + actual_module_name, actual_version = (module, version) > + if '://' in repo_url: > + repo_url_noprotocol = repo_url.split('://')[1] > + else: > + repo_url_noprotocol = repo_url > + if not repo_url.startswith('https://'): > + repo_url = 'https://' + repo_url > + name = module.split('/')[-1] > + if name in srcrev_name_recorded: > + name = '-'.join(module.split('/')[-2:]) > + src_uri_contents.append((actual_module_name, > actual_version, repo_url, fullrev, name, fullrev, repo_url_noprotocol, > name, actual_module_name)) > + srcrev_name_recorded.append(name) > + # sort the src_uri_contents and then write it > + src_uri_contents.sort(key=take_first_len) > + for content in src_uri_contents: > + f.write(template % content) > + logger.info("%s generated" % src_uri_inc_file) > + > + def gen_relocation_inc(self): > + """ > + Generate relocation.inc file > + """ > + relocation_inc_file = os.path.join(self.workdir, 'relocation.inc') > + template = """export sites="%s" > + > +do_compile:prepend() { > + cd ${S}/src/import > + for s in $sites; do > + site_dest=$(echo $s | cut -d: -f1) > + site_source=$(echo $s | cut -d: -f2) > + force_flag=$(echo $s | cut -d: -f3) > + mkdir -p vendor.copy/$site_dest > + if [ -n "$force_flag" ]; then > + echo "[INFO] $site_dest: force copying .go files" > + rm -rf vendor.copy/$site_dest > + rsync -a --exclude='vendor/' --exclude='.git/' > vendor.fetch/$site_source/ vendor.copy/$site_dest > + else > + [ -n "$(ls -A vendor.copy/$site_dest/*.go 2> /dev/null)" ] && > { echo "[INFO] vendor.fetch/$site_source -> $site_dest: go copy skipped > (files present)" ; true ; } || { echo "[INFO] $site_dest: copying .go > files" ; rsync -a --exclude='vendor/' --exclude='.git/' > vendor.fetch/$site_source/ vendor.copy/$site_dest ; } > + fi > + done > +} > +""" > + sites = [] > + for module in self.modules_require: > + # <dest>:<source>[:force] > + if module in self.modules_srcpaths: > + srcpath = self.modules_srcpaths[module] > + logger.debug("Using %s as srcpath of module (%s)" % > (srcpath, module)) > + else: > + srcpath = module > + sites.append("%s:%s:force" % (module, srcpath)) > + # To avoid the former one being overriden by the latter one when > the former one is a subpath of the latter one, sort sites > + sites.sort(key=len) > + with open(relocation_inc_file, 'w') as f: > + sites_str = ' \\\n '.join(sites) > + f.write(template % sites_str) > + logger.info("%s generated" % relocation_inc_file) > + > + def gen_modules_txt(self): > + """ > + Generate modules.txt file > + """ > + modules_txt_file = os.path.join(self.workdir, 'modules.txt') > + with open(modules_txt_file, 'w') as f: > + for l in self.require_lines: > + f.write('# %s\n' % l) > + f.write('## explicit\n') > + for l in self.replace_lines: > + f.write('# %s\n' %l) > + logger.info("%s generated" % modules_txt_file) > + > + def sanity_check(self): > + """ > + Various anity checks > + """ > + sanity_check_ok = True > + # > + # Sanity Check 1: > + # For modules having the same repo, at most one is allowed to > not have subpath. > + # This check operates on self.modules_repoinfo and > self.modules_subpaths > + # > + repo_modules = {} > + for module in self.modules_repoinfo: > + # first form {repo: [module1, module2, ...]} > + repo_url, repo_dest_dir, fullsrcrev = > self.modules_repoinfo[module] > + if repo_url not in repo_modules: > + repo_modules[repo_url] = [module] > + else: > + repo_modules[repo_url].append(module) > + for repo in repo_modules: > + modules = repo_modules[repo] > + if len(modules) == 1: > + continue > + # for modules sharing the same repo, at most one is allowed > to not have subpath > + nosubpath_modules = [] > + for m in modules: > + if m not in self.modules_subpaths: > + nosubpath_modules.append(m) > + if len(nosubpath_modules) == 0: > + continue > + if len(nosubpath_modules) > 1: > + logger.warning("Multiple modules sharing %s, but they > don't have subpath: %s. Please double check." % (repo, nosubpath_modules)) > + if len(nosubpath_modules) == 1: > + # do further check, OK if the module is the prefix for > other modules sharing the same repo > + module_to_check = nosubpath_modules[0] > + for m in modules: > + if module_to_check == m: > + continue > + if not m.startswith('%s/' % module_to_check): > + logger.warning("%s is sharing repo (%s) with > other modules, and it might need a subpath. Please double check" % > (module_to_check, repo)) > + continue > + > + # > + # End of Sanity Check > + # > + if not sanity_check_ok: > + sys.exit(1) > + return > + > +def main(): > + parser = argparse.ArgumentParser( > + description="oe-go-mod-autogen.py is used to generate > src_uri.inc, relocation.inc and modules.txt to be used by go mod recipes", > + epilog="Use %(prog)s --help to get help") > + parser.add_argument("--repo", help = "Repo for the recipe.", > required=True) > + parser.add_argument("--rev", help = "Revision for the recipe.", > required=True) > + parser.add_argument("--module", help = "Go module name. To be used > with '--test'") > + parser.add_argument("--version", help = "Go module version. To be > used with '--test'") > + parser.add_argument("--test", help = "Test to get repo url and > fullsrcrev, used together with --module and --version.", > action="store_true") > + parser.add_argument("--workdir", help = "Working directory to hold > intermediate results and output.", default=os.getcwd()) > + parser.add_argument("-d", "--debug", > + help = "Enable debug output", > + action="store_const", const=logging.DEBUG, > dest="loglevel", default=logging.INFO) > + parser.add_argument("-q", "--quiet", > + help = "Hide all output except error messages", > + action="store_const", const=logging.ERROR, > dest="loglevel") > + args = parser.parse_args() > + > + logger.setLevel(args.loglevel) > + logger.debug("oe-go-mod-autogen.py running for %s:%s in %s" % > (args.repo, args.rev, args.workdir)) > + gomodtool = GoModTool(args.repo, args.rev, args.workdir) > + if args.test: > + if not args.module or not args.version: > + print("Please specify --module and --version") > + sys.exit(1) > + url, srcrev = gomodtool.get_url_srcrev(args.module, args.version) > + print("url = %s, srcrev = %s" % (url, srcrev)) > + if not url or not srcrev: > + print("Failed to get url & srcrev for %s:%s" % (args.module, > args.version)) > + else: > + gomodtool.parse() > + gomodtool.sanity_check() > + gomodtool.gen_src_uri_inc() > + gomodtool.gen_relocation_inc() > + gomodtool.gen_modules_txt() > + > + > +if __name__ == "__main__": > + try: > + ret = main() > + except Exception as esc: > + ret = 1 > + import traceback > + traceback.print_exc() > + sys.exit(ret) > -- > 2.40.0 > > > > > -- Best regards, José Quaresma
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#8275): https://lists.yoctoproject.org/g/meta-virtualization/message/8275 Mute This Topic: https://lists.yoctoproject.org/mt/101162746/21656 Group Owner: meta-virtualization+ow...@lists.yoctoproject.org Unsubscribe: https://lists.yoctoproject.org/g/meta-virtualization/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-