summaryrefslogtreecommitdiffstats
path: root/meta/classes/go-vendor.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes/go-vendor.bbclass')
-rw-r--r--meta/classes/go-vendor.bbclass200
1 files changed, 200 insertions, 0 deletions
diff --git a/meta/classes/go-vendor.bbclass b/meta/classes/go-vendor.bbclass
new file mode 100644
index 0000000000..5b017b0b9d
--- /dev/null
+++ b/meta/classes/go-vendor.bbclass
@@ -0,0 +1,200 @@
1#
2# Copyright 2023 (C) Weidmueller GmbH & Co KG
3# Author: Lukas Funke <lukas.funke@weidmueller.com>
4#
5# Handle Go vendor support for offline builds
6#
7# When importing Go modules, Go downloads the imported modules using
8# a network (proxy) connection ahead of the compile stage. This contradicts
9# the yocto build concept of fetching every source ahead of build-time
10# and supporting offline builds.
11#
12# To support offline builds, we use Go 'vendoring': module dependencies are
13# downloaded during the fetch-phase and unpacked into the modules 'vendor'
14# folder. Additionally a manifest file is generated for the 'vendor' folder
15#
16
17inherit go-mod
18
19def go_src_uri(repo, version, path=None, subdir=None, \
20 vcs='git', replaces=None, pathmajor=None):
21
22 destsuffix = "git/src/import/vendor.fetch"
23 module_path = repo if not path else path
24
25 src_uri = "{}://{};name={}".format(vcs, repo, module_path.replace('/', '.'))
26 src_uri += ";destsuffix={}/{}@{}".format(destsuffix, repo, version)
27
28 if vcs == "git":
29 src_uri += ";nobranch=1;protocol=https"
30
31 src_uri += ";go_module_path={}".format(module_path)
32
33 if replaces:
34 src_uri += ";go_module_replacement={}".format(replaces)
35 if subdir:
36 src_uri += ";go_subdir={}".format(subdir)
37 if pathmajor:
38 src_uri += ";go_pathmajor={}".format(pathmajor)
39 src_uri += ";is_go_dependency=1"
40
41 return src_uri
42
43python do_vendor_unlink() {
44
45 # We unlink
46
47 go_import = d.getVar('GO_IMPORT')
48 source_dir = d.getVar('S')
49 linkname = os.path.join(source_dir, *['src', go_import, 'vendor'])
50
51 os.unlink(linkname)
52}
53
54addtask vendor_unlink before do_install after do_compile
55
56python do_go_vendor() {
57 import shutil
58
59 src_uri = (d.getVar('SRC_URI') or "").split()
60
61 if len(src_uri) == 0:
62 bb.error("SRC_URI is empty")
63 return
64
65 default_destsuffix = "git/src/import/vendor.fetch"
66 fetcher = bb.fetch2.Fetch(src_uri, d)
67 go_import = d.getVar('GO_IMPORT')
68 source_dir = d.getVar('S')
69
70 linkname = os.path.join(source_dir, *['src', go_import, 'vendor'])
71 vendor_dir = os.path.join(source_dir, *['src', 'import', 'vendor'])
72 import_dir = os.path.join(source_dir, *['src', 'import', 'vendor.fetch'])
73
74 if os.path.exists(vendor_dir):
75 # Nothing to do except re-establish link to actual vendor folder
76 if not os.path.exists(linkname):
77 os.symlink(vendor_dir, linkname)
78 return
79
80 bb.utils.mkdirhier(vendor_dir)
81
82 modules = {}
83
84 for url in fetcher.urls:
85 srcuri = fetcher.ud[url].host + fetcher.ud[url].path
86
87 # Skip non Go module src uris
88 if not fetcher.ud[url].parm.get('is_go_dependency'):
89 continue
90
91 destsuffix = fetcher.ud[url].parm.get('destsuffix')
92 # We derive the module repo / version in the following manner (exmaple):
93 #
94 # destsuffix = git/src/import/vendor.fetch/github.com/foo/bar@v1.2.3
95 # p = github.com/foo/bar@v1.2.3
96 # repo = github.com/foo/bar
97 # version = v1.2.3
98
99 p = destsuffix[len(default_destsuffix)+1:]
100 repo, version = p.split('@')
101
102 module_path = fetcher.ud[url].parm.get('go_module_path')
103
104 subdir = fetcher.ud[url].parm.get('go_subdir')
105 subdir = None if not subdir else subdir
106
107 pathMajor = fetcher.ud[url].parm.get('go_pathmajor')
108 pathMajor = None if not pathMajor else pathMajor.strip('/')
109
110 if not repo in modules:
111 modules[repo] = { "version": version,
112 "repo_path": os.path.join(import_dir, p),
113 "module_path": module_path,
114 "subdir": subdir,
115 "pathMajor": pathMajor }
116
117 for module_key in sorted(modules):
118
119 # only take the version which is explicitly listed
120 # as a dependency in the go.mod
121 module = modules[module_key]
122 module_path = module['module_path']
123 rootdir = module['repo_path']
124 subdir = module['subdir']
125 pathMajor = module['pathMajor']
126
127 src = rootdir
128
129 if subdir:
130 src = os.path.join(rootdir, subdir)
131
132 # If the module is released at major version 2 or higher, the module
133 # path must end with a major version suffix like /v2.
134 # This may or may not be part of the subdirectory name
135 #
136 # https://go.dev/ref/mod#modules-overview
137 if pathMajor:
138 tmp = os.path.join(src, pathMajor)
139 # source directory including major version path may or may not exist
140 if os.path.exists(tmp):
141 src = tmp
142
143 dst = os.path.join(vendor_dir, module_path)
144
145 bb.debug(1, "cp %s --> %s" % (src, dst))
146 shutil.copytree(src, dst, symlinks=True, \
147 ignore=shutil.ignore_patterns(".git", \
148 "vendor", \
149 "*._test.go"))
150
151 # If the root directory has a LICENSE file but not the subdir
152 # we copy the root license to the sub module since the license
153 # applies to all modules in the repository
154 # see https://go.dev/ref/mod#vcs-license
155 if subdir:
156 rootdirLicese = os.path.join(rootdir, "LICENSE")
157 subdirLicense = os.path.join(src, "LICENSE")
158
159 if not os.path.exists(subdir) and \
160 os.path.exists(rootdirLicese):
161 shutil.copy2(rootdirLicese, subdirLicense)
162
163 # Copy vendor manifest
164 modules_txt_src = os.path.join(d.getVar('WORKDIR'), "modules.txt")
165 bb.debug(1, "cp %s --> %s" % (modules_txt_src, vendor_dir))
166 shutil.copy2(modules_txt_src, vendor_dir)
167
168 # Clean up vendor dir
169 # We only require the modules in the modules_txt file
170 fetched_paths = set([os.path.relpath(x[0], vendor_dir) for x in os.walk(vendor_dir)])
171
172 # Remove toplevel dir
173 fetched_paths.remove('.')
174
175 vendored_paths = set()
176 with open(modules_txt_src) as f:
177 for line in f:
178 if not line.startswith("#"):
179 line = line.strip()
180 vendored_paths.add(line)
181
182 # Add toplevel dirs into vendored dir, as we want to keep them
183 topdir = os.path.dirname(line)
184 while len(topdir):
185 if not topdir in vendored_paths:
186 vendored_paths.add(topdir)
187
188 topdir = os.path.dirname(topdir)
189
190 for path in fetched_paths:
191 if path not in vendored_paths:
192 realpath = os.path.join(vendor_dir, path)
193 if os.path.exists(realpath):
194 shutil.rmtree(realpath)
195
196 # Create a symlink the the actual directory
197 os.symlink(vendor_dir, linkname)
198}
199
200addtask go_vendor before do_patch after do_unpack