1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# Recipe creation tool - go support plugin
#
# The code is based on golang internals. See the afftected
# methods for further reference and information.
#
# Copyright (C) 2023 Weidmueller GmbH & Co KG
# Author: Lukas Funke <lukas.funke@weidmueller.com>
#
# SPDX-License-Identifier: GPL-2.0-only
#
from recipetool.create import RecipeHandler, handle_license_vars
import bb.utils
import json
import logging
import os
import re
import subprocess
import sys
import tempfile
logger = logging.getLogger('recipetool')
tinfoil = None
def tinfoil_init(instance):
global tinfoil
tinfoil = instance
class GoRecipeHandler(RecipeHandler):
"""Class to handle the go recipe creation"""
@staticmethod
def __ensure_go():
"""Check if the 'go' command is available in the recipes"""
recipe = "go-native"
if not tinfoil.recipes_parsed:
tinfoil.parse_recipes()
try:
rd = tinfoil.parse_recipe(recipe)
except bb.providers.NoProvider:
bb.error(
"Nothing provides '%s' which is required for the build" % (recipe))
bb.note(
"You will likely need to add a layer that provides '%s'" % (recipe))
return None
bindir = rd.getVar('STAGING_BINDIR_NATIVE')
gopath = os.path.join(bindir, 'go')
if not os.path.exists(gopath):
tinfoil.build_targets(recipe, 'addto_recipe_sysroot')
if not os.path.exists(gopath):
logger.error(
'%s required to process specified source, but %s did not seem to populate it' % 'go', recipe)
return None
return bindir
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 = self.__ensure_go()
if not go_bindir:
sys.exit(14)
handled.append('buildsystem')
classes.append("go-mod")
# Use go-mod-update-modules to set the full SRC_URI and LICENSE
classes.append("go-mod-update-modules")
extravalues["run_tasks"] = "update_modules"
with tempfile.TemporaryDirectory(prefix="go-mod-") as tmp_mod_dir:
env = dict(os.environ)
env["PATH"] += f":{go_bindir}"
env['GOMODCACHE'] = tmp_mod_dir
stdout = subprocess.check_output(["go", "mod", "edit", "-json"], cwd=srctree, env=env, text=True)
go_mod = json.loads(stdout)
go_import = re.sub(r'/v([0-9]+)$', '', go_mod['Module']['Path'])
localfilesdir = tempfile.mkdtemp(prefix='recipetool-go-')
extravalues.setdefault('extrafiles', {})
# Write the stub ${BPN}-licenses.inc and ${BPN}-go-mods.inc files
basename = "{pn}-licenses.inc"
filename = os.path.join(localfilesdir, basename)
with open(filename, "w") as f:
f.write("# FROM RECIPETOOL\n")
extravalues['extrafiles'][f"../{basename}"] = filename
basename = "{pn}-go-mods.inc"
filename = os.path.join(localfilesdir, basename)
with open(filename, "w") as f:
f.write("# FROM RECIPETOOL\n")
extravalues['extrafiles'][f"../{basename}"] = filename
# Do generic license handling
d = bb.data.createCopy(tinfoil.config_data)
handle_license_vars(srctree, lines_before, handled, extravalues, d)
self.__rewrite_lic_vars(lines_before)
self.__rewrite_src_uri(lines_before)
lines_before.append('require ${BPN}-licenses.inc')
lines_before.append('require ${BPN}-go-mods.inc')
lines_before.append(f'GO_IMPORT = "{go_import}"')
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_vars(self, lines_before):
def varfunc(varname, origvalue, op, newlines):
import urllib.parse
if varname == 'LIC_FILES_CHKSUM':
new_licenses = []
licenses = origvalue.split('\\')
for license in licenses:
if not license:
logger.warning("No license file was detected for the main module!")
# the license list of the main recipe must be empty
# this can happen for example in case of CLOSED license
# Fall through to complete recipe generation
continue
license = license.strip()
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, lines_before):
def varfunc(varname, origvalue, op, newlines):
if varname == 'SRC_URI':
src_uri = ['git://${GO_IMPORT};protocol=https;nobranch=1;destsuffix=${GO_SRCURI_DESTSUFFIX}']
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))
|