summaryrefslogtreecommitdiffstats
path: root/meta/classes-recipe/go-mod-update-modules.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes-recipe/go-mod-update-modules.bbclass')
-rw-r--r--meta/classes-recipe/go-mod-update-modules.bbclass152
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..5fccd0bb0d
--- /dev/null
+++ b/meta/classes-recipe/go-mod-update-modules.bbclass
@@ -0,0 +1,152 @@
1addtask do_update_modules after do_configure
2do_update_modules[nostamp] = "1"
3do_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
15python 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("${UNPACKDIR}/${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 not mod_dir.startswith(mod_cache_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"