diff options
| author | Ross Burton <ross.burton@arm.com> | 2025-06-27 14:48:47 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2025-07-01 08:49:37 +0100 |
| commit | 9291f67f1e01fe58dab7c28cbc2bd443da7950dc (patch) | |
| tree | 53273627caa468b95b1926d28a9a900635cd9b13 /meta | |
| parent | 72486700fb17153bb777a1fdf4abeb6d249a2657 (diff) | |
| download | poky-9291f67f1e01fe58dab7c28cbc2bd443da7950dc.tar.gz | |
classes/go-mod-update-modules: add class to generate module list
Almost entirely based on the create_go.py module for recipetool by
Christian Lindeberg <christian.lindeberg@axis.com>, this instead has the
logic inside a class that can be used to update the list of Go modules
that are used, both SRC_URI and LICENSE.
Integration with devtool upgrade will come shortly, but it needs a bit
more work.
(From OE-Core rev: 34bb889ffaae15f89c5627610826b498697c51f2)
Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta')
| -rw-r--r-- | meta/classes-recipe/go-mod-update-modules.bbclass | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/meta/classes-recipe/go-mod-update-modules.bbclass b/meta/classes-recipe/go-mod-update-modules.bbclass new file mode 100644 index 0000000000..6ef7ab553b --- /dev/null +++ b/meta/classes-recipe/go-mod-update-modules.bbclass | |||
| @@ -0,0 +1,152 @@ | |||
| 1 | addtask do_update_modules after do_configure | ||
| 2 | do_update_modules[nostamp] = "1" | ||
| 3 | do_update_modules[network] = "1" | ||
| 4 | |||
| 5 | # This class maintains two files, BPN-go-mods.inc and BPN-licenses.inc. | ||
| 6 | # | ||
| 7 | # -go-mods.inc will append SRC_URI with all of the Go modules that are | ||
| 8 | # dependencies of this recipe. | ||
| 9 | # | ||
| 10 | # -licenses.inc will append LICENSE and LIC_FILES_CHKSUM with the found licenses | ||
| 11 | # in the modules. | ||
| 12 | # | ||
| 13 | # These files are machine-generated and should not be modified. | ||
| 14 | |||
| 15 | python do_update_modules() { | ||
| 16 | import subprocess, tempfile, json, re, urllib.parse | ||
| 17 | from oe.license import tidy_licenses | ||
| 18 | from oe.license_finder import find_licenses | ||
| 19 | |||
| 20 | def unescape_path(path): | ||
| 21 | """Unescape capital letters using exclamation points.""" | ||
| 22 | return re.sub(r'!([a-z])', lambda m: m.group(1).upper(), path) | ||
| 23 | |||
| 24 | def fold_uri(uri): | ||
| 25 | """Fold URI for sorting shorter module paths before longer.""" | ||
| 26 | return uri.replace(';', ' ').replace('/', '!') | ||
| 27 | |||
| 28 | def parse_existing_licenses(): | ||
| 29 | hashes = {} | ||
| 30 | for url in d.getVar("LIC_FILES_CHKSUM").split(): | ||
| 31 | (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url) | ||
| 32 | if "spdx" in parm and parm["spdx"] != "Unknown": | ||
| 33 | hashes[parm["md5"]] = urllib.parse.unquote_plus(parm["spdx"]) | ||
| 34 | return hashes | ||
| 35 | |||
| 36 | bpn = d.getVar("BPN") | ||
| 37 | thisdir = d.getVar("THISDIR") | ||
| 38 | s_dir = d.getVar("S") | ||
| 39 | |||
| 40 | with tempfile.TemporaryDirectory(prefix='go-mod-') as mod_cache_dir: | ||
| 41 | notice = """ | ||
| 42 | # This file has been generated by go-mod-update-modules.bbclass | ||
| 43 | # | ||
| 44 | # Do not modify it by hand, as the contents will be replaced when | ||
| 45 | # running the update-modules task. | ||
| 46 | |||
| 47 | """ | ||
| 48 | |||
| 49 | env = dict(os.environ, GOMODCACHE=mod_cache_dir) | ||
| 50 | |||
| 51 | source = d.expand("${WORKDIR}/${GO_SRCURI_DESTSUFFIX}") | ||
| 52 | output = subprocess.check_output(("go", "mod", "edit", "-json"), cwd=source, env=env, text=True) | ||
| 53 | go_mod = json.loads(output) | ||
| 54 | |||
| 55 | output = subprocess.check_output(("go", "list", "-json=Dir,Module", "-deps", f"{go_mod['Module']['Path']}/..."), cwd=source, env=env, text=True) | ||
| 56 | |||
| 57 | # | ||
| 58 | # Licenses | ||
| 59 | # | ||
| 60 | |||
| 61 | # load hashes from the existing licenses.inc | ||
| 62 | extra_hashes = parse_existing_licenses() | ||
| 63 | |||
| 64 | # The output of this isn't actually valid JSON, but a series of dicts. | ||
| 65 | # Wrap in [] and join the dicts with , | ||
| 66 | # Very frustrating that the json parser in python can't repeatedly | ||
| 67 | # parse from a stream. | ||
| 68 | pkgs = json.loads('[' + output.replace('}\n{', '},\n{') + ']') | ||
| 69 | # Collect licenses for the dependencies. | ||
| 70 | licenses = set() | ||
| 71 | lic_files_chksum = [] | ||
| 72 | lic_files = {} | ||
| 73 | |||
| 74 | for pkg in pkgs: | ||
| 75 | mod = pkg.get('Module', None) | ||
| 76 | if not mod or mod.get('Main', False): | ||
| 77 | continue | ||
| 78 | |||
| 79 | mod_dir = mod['Dir'] | ||
| 80 | |||
| 81 | if mod_dir.startswith(s_dir): | ||
| 82 | continue | ||
| 83 | |||
| 84 | path = os.path.relpath(mod_dir, mod_cache_dir) | ||
| 85 | |||
| 86 | for license_name, license_file, license_md5 in find_licenses(mod['Dir'], d, first_only=True, extra_hashes=extra_hashes): | ||
| 87 | lic_files[os.path.join(path, license_file)] = (license_name, license_md5) | ||
| 88 | |||
| 89 | for lic_file in lic_files: | ||
| 90 | license_name, license_md5 = lic_files[lic_file] | ||
| 91 | if license_name == "Unknown": | ||
| 92 | bb.warn(f"Unknown license: {lic_file} {license_md5}") | ||
| 93 | |||
| 94 | licenses.add(lic_files[lic_file][0]) | ||
| 95 | lic_files_chksum.append( | ||
| 96 | f'file://pkg/mod/{lic_file};md5={license_md5};spdx={urllib.parse.quote_plus(license_name)}') | ||
| 97 | |||
| 98 | licenses_filename = os.path.join(thisdir, f"{bpn}-licenses.inc") | ||
| 99 | with open(licenses_filename, "w") as f: | ||
| 100 | f.write(notice) | ||
| 101 | f.write(f'LICENSE += "& {" & ".join(tidy_licenses(licenses))}"\n\n') | ||
| 102 | f.write('LIC_FILES_CHKSUM += "\\\n') | ||
| 103 | for lic in sorted(lic_files_chksum, key=fold_uri): | ||
| 104 | f.write(' ' + lic + ' \\\n') | ||
| 105 | f.write('"\n') | ||
| 106 | |||
| 107 | # | ||
| 108 | # Sources | ||
| 109 | # | ||
| 110 | |||
| 111 | # Collect the module cache files downloaded by the go list command as | ||
| 112 | # the go list command knows best what the go list command needs and it | ||
| 113 | # needs more files in the module cache than the go install command as | ||
| 114 | # it doesn't do the dependency pruning mentioned in the Go module | ||
| 115 | # reference, https://go.dev/ref/mod, for go 1.17 or higher. | ||
| 116 | src_uris = [] | ||
| 117 | downloaddir = os.path.join(mod_cache_dir, 'cache', 'download') | ||
| 118 | for dirpath, _, filenames in os.walk(downloaddir): | ||
| 119 | # We want to process files under @v directories | ||
| 120 | path, base = os.path.split(os.path.relpath(dirpath, downloaddir)) | ||
| 121 | if base != '@v': | ||
| 122 | continue | ||
| 123 | |||
| 124 | path = unescape_path(path) | ||
| 125 | zipver = None | ||
| 126 | for name in filenames: | ||
| 127 | ver, ext = os.path.splitext(name) | ||
| 128 | if ext == '.zip': | ||
| 129 | chksum = bb.utils.sha256_file(os.path.join(dirpath, name)) | ||
| 130 | src_uris.append(f'gomod://{path};version={ver};sha256sum={chksum}') | ||
| 131 | zipver = ver | ||
| 132 | break | ||
| 133 | for name in filenames: | ||
| 134 | ver, ext = os.path.splitext(name) | ||
| 135 | if ext == '.mod' and ver != zipver: | ||
| 136 | chksum = bb.utils.sha256_file(os.path.join(dirpath, name)) | ||
| 137 | src_uris.append(f'gomod://{path};version={ver};mod=1;sha256sum={chksum}') | ||
| 138 | |||
| 139 | |||
| 140 | go_mods_filename = os.path.join(thisdir, f"{bpn}-go-mods.inc") | ||
| 141 | with open(go_mods_filename, "w") as f: | ||
| 142 | f.write(notice) | ||
| 143 | f.write('SRC_URI += "\\\n') | ||
| 144 | for uri in sorted(src_uris, key=fold_uri): | ||
| 145 | f.write(' ' + uri + ' \\\n') | ||
| 146 | f.write('"\n') | ||
| 147 | |||
| 148 | subprocess.check_output(("go", "clean", "-modcache"), cwd=source, env=env, text=True) | ||
| 149 | } | ||
| 150 | |||
| 151 | # This doesn't work as we need to wipe the inc files first so we don't try looking for LICENSE files that don't yet exist | ||
| 152 | # RECIPE_UPGRADE_EXTRA_TASKS += "do_update_modules" | ||
