summaryrefslogtreecommitdiffstats
path: root/scripts/lib/recipetool/create_go.py
blob: 4b1fa39d13a0542c98946cd78a157d63fcdbd992 (plain)
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))