summaryrefslogtreecommitdiffstats
path: root/scripts/oe-go-mod-autogen.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/oe-go-mod-autogen.py')
-rwxr-xr-xscripts/oe-go-mod-autogen.py663
1 files changed, 663 insertions, 0 deletions
diff --git a/scripts/oe-go-mod-autogen.py b/scripts/oe-go-mod-autogen.py
new file mode 100755
index 00000000..09d6133b
--- /dev/null
+++ b/scripts/oe-go-mod-autogen.py
@@ -0,0 +1,663 @@
1#!/usr/bin/env python3
2
3import os
4import sys
5import logging
6import argparse
7from collections import OrderedDict
8import subprocess
9
10# This switch is used to make this script error out ASAP, mainly for debugging purpose
11ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE = True
12
13logger = logging.getLogger('oe-go-mod-autogen')
14loggerhandler = logging.StreamHandler()
15loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
16logger.addHandler(loggerhandler)
17logger.setLevel(logging.INFO)
18
19class GoModTool(object):
20 def __init__(self, repo, rev, workdir):
21 self.repo = repo
22 self.rev = rev
23 self.workdir = workdir
24
25 # Stores the actual module name and its related information
26 # {module: (repo_url, repo_dest_dir, fullsrcrev)}
27 self.modules_repoinfo = {}
28
29 # {module_name: (url, version, destdir, fullsrcrev)}
30 #
31 # url: place to get the source codes, we only support git repo
32 # version: module version, git tag or git rev
33 # destdir: place to put the fetched source codes
34 # fullsrcrev: full src rev which is the value of SRC_REV
35 #
36 # e.g.
37 # For 'github.com/Masterminds/semver/v3 v3.1.1' in go.mod:
38 # module_name = github.com/Masterminds/semver/v3
39 # url = https://github.com/Masterminds/semver
40 # version = v3.1.1
41 # destdir = ${WORKDIR}/${BP}/src/${GO_IMPORT}/vendor/github.com/Masterminds/semver/v3
42 # fullsrcrev = d387ce7889a157b19ad7694dba39a562051f41b0
43 self.modules_require = OrderedDict()
44
45 # {orig_module: (actual_module, actual_version)}
46 self.modules_replace = OrderedDict()
47
48 # Unhandled modules
49 self.modules_unhandled = OrderedDict()
50
51 # store subpaths used to form srcpath
52 # {actual_module_name: subpath}
53 self.modules_subpaths = OrderedDict()
54
55 # modules's actual source paths, record those that are not the same with the module itself
56 self.modules_srcpaths = OrderedDict()
57
58 # store lines, comment removed
59 self.require_lines = []
60 self.replace_lines = []
61
62 # fetch repo
63 self.fetch_and_checkout_repo(self.repo.split('://')[1], self.repo, self.rev, checkout=True, get_subpath=False)
64
65 def show_go_mod_info(self):
66 # Print modules_require, modules_replace and modules_unhandled
67 print("modules required:")
68 for m in self.modules_require:
69 url, version, destdir, fullrev = self.modules_require[m]
70 print("%s %s %s %s" % (m, version, url, fullrev))
71
72 print("modules replace:")
73 for m in self.modules_replace:
74 actual_module, actual_version = self.modules_replace[m]
75 print("%s => %s %s" % (m, actual_module, actual_version))
76
77 print("modules unhandled:")
78 for m in self.modules_unhandled:
79 reason = self.modules_unhandled[m]
80 print("%s unhandled: %s" % (m, reason))
81
82 def parse(self):
83 # check if this repo needs autogen
84 repo_url, repo_dest_dir, repo_fullrev = self.modules_repoinfo[self.repo.split('://')[1]]
85 if os.path.isdir(os.path.join(repo_dest_dir, 'vendor')):
86 logger.info("vendor direcotry has already existed for %s, no need to add other repos" % self.repo)
87 return
88 go_mod_file = os.path.join(repo_dest_dir, 'go.mod')
89 if not os.path.exists(go_mod_file):
90 logger.info("go.mod file does not exist for %s, no need to add otehr repos" % self.repo)
91 return
92 self.parse_go_mod(go_mod_file)
93 self.show_go_mod_info()
94
95 def fetch_and_checkout_repo(self, module_name, repo_url, rev, default_protocol='https://', checkout=False, get_subpath=True):
96 """
97 Fetch repo_url to <workdir>/repos/repo_base_name
98 """
99 protocol = default_protocol
100 if '://' in repo_url:
101 repo_url_final = repo_url
102 else:
103 repo_url_final = default_protocol + repo_url
104 logger.debug("fetch and checkout %s %s" % (repo_url_final, rev))
105 repos_dir = os.path.join(self.workdir, 'repos')
106 if not os.path.exists(repos_dir):
107 os.makedirs(repos_dir)
108 repo_basename = repo_url.split('/')[-1].split('.git')[0]
109 repo_dest_dir = os.path.join(repos_dir, repo_basename)
110 module_last_name = module_name.split('/')[-1]
111 git_action = "fetch"
112 if os.path.exists(repo_dest_dir):
113 if checkout:
114 # check if current HEAD is rev
115 try:
116 headrev = subprocess.check_output('git rev-list -1 HEAD', shell=True, cwd=repo_dest_dir).decode('utf-8').strip()
117 requiredrev = subprocess.check_output('git rev-list -1 %s 2>/dev/null || git rev-list -1 %s/%s' % (rev, module_last_name, rev), shell=True, cwd=repo_dest_dir).decode('utf-8').strip()
118 if headrev == requiredrev:
119 logger.info("%s has already been fetched and checked out as required, skipping" % repo_url)
120 self.modules_repoinfo[module_name] = (repo_url, repo_dest_dir, requiredrev)
121 return
122 else:
123 logger.info("HEAD of %s is not %s, will do a clean clone" % (repo_dest_dir, requiredrev))
124 git_action = "clone"
125 except:
126 logger.info("'git rev-list' in %s failed, will do a clean clone" % repo_dest_dir)
127 git_action = "clone"
128 else:
129 # determine if the current repo points to the desired remote repo
130 try:
131 remote_origin_url = subprocess.check_output('git config --get remote.origin.url', shell=True, cwd=repo_dest_dir).decode('utf-8').strip()
132 if remote_origin_url.endswith('.git'):
133 if not repo_url_final.endswith('.git'):
134 remote_origin_url = remote_origin_url[:-4]
135 else:
136 if repo_url_final.endswith('.git'):
137 remote_origin_url = remote_origin_url + '.git'
138 if remote_origin_url != repo_url_final:
139 logger.info("remote.origin.url for %s is not %s, will do a clean clone" % (repo_dest_dir, repo_url_final))
140 git_action = "clone"
141 except:
142 logger.info("'git config --get remote.origin.url' in %s failed, will do a clean clone" % repo_dest_dir)
143 git_action = "clone"
144 else:
145 # No local repo, clone it.
146 git_action = "clone"
147
148 if git_action == "clone":
149 logger.info("Removing %s" % repo_dest_dir)
150 subprocess.check_call('rm -rf %s' % repo_dest_dir, shell=True)
151
152 # clone/fetch repo
153 try:
154 git_cwd = repos_dir if git_action == "clone" else repo_dest_dir
155 logger.info("git %s %s in %s" % (git_action, repo_url_final, git_cwd))
156 subprocess.check_call('git %s %s >/dev/null 2>&1' % (git_action, repo_url_final), shell=True, cwd=git_cwd)
157 except:
158 logger.warning("Failed to %s %s in %s" % (git_action, repo_url_final, git_cwd))
159 return
160
161 def get_requiredrev(get_subpath):
162 import re
163 # check if rev is a revision or a version
164 if len(rev) == 12 and re.match('[0-9a-f]+', rev):
165 rev_is_version = False
166 else:
167 rev_is_version = True
168
169 # if rev is not a version, 'git rev-list -1 <rev>' should just succeed!
170 if not rev_is_version:
171 try:
172 rev_return = subprocess.check_output('git rev-list -1 %s 2>/dev/null' % rev, shell=True, cwd=repo_dest_dir).decode('utf-8').strip()
173 if get_subpath:
174 cmd = 'git branch -M toremove && git checkout -b check_subpath %s && git branch -D toremove' % rev_return
175 subprocess.check_call(cmd, shell=True, cwd=repo_dest_dir)
176 # try to get the subpath for this module
177 module_name_parts = module_name.split('/')
178 while (len(module_name_parts) > 0):
179 subpath = '/'.join(module_name_parts)
180 dir_to_check = repo_dest_dir + '/' + '/'.join(module_name_parts)
181 if os.path.isdir(dir_to_check):
182 self.modules_subpaths[module_name] = subpath
183 break
184 else:
185 module_name_parts.pop(0)
186 return rev_return
187 except:
188 logger.warning("Revision (%s) not in repo(%s)" % (rev, repo_dest_dir))
189 return None
190
191 # the following codes deals with case where rev is a version
192 # determine the longest match tag, in this way, we can get the current srcpath to be used in relocation.inc
193 # we first get the initial tag, which is formed from module_name and rev
194 module_parts = module_name.split('/')
195 if rev.startswith(module_parts[-1] + '.'):
196 tag = '/'.join(module_parts[:-1]) + '/' + rev
197 last_module_part_replaced = True
198 else:
199 tag = '/'.join(module_parts) + '/' + rev
200 last_module_part_replaced = False
201 logger.debug("use %s as the initial tag for %s" % (tag, module_name))
202 tag_parts = tag.split('/')
203 while(len(tag_parts) > 0):
204 try:
205 rev_return = subprocess.check_output('git rev-list -1 %s 2>/dev/null' % tag, shell=True, cwd=repo_dest_dir).decode('utf-8').strip()
206 if len(tag_parts) > 1:
207 # ensure that the subpath exists
208 if get_subpath:
209 cmd = 'git branch -M toremove && git checkout -b check_subpath %s && git branch -D toremove' % rev_return
210 subprocess.check_call(cmd, shell=True, cwd=repo_dest_dir)
211 # get subpath for the actual_module_name
212 if last_module_part_replaced:
213 subpath = '/'.join(tag_parts[:-1]) + '/' + module_parts[-1]
214 if not os.path.isdir(repo_dest_dir + '/' + subpath):
215 subpath = '/'.join(tag_parts[:-1])
216 else:
217 subpath = '/'.join(tag_parts[:-1])
218 if not os.path.isdir(repo_dest_dir + '/' + subpath):
219 logger.warning("subpath (%s) derived from tag matching does not exist in %s" % (subpath, repo_dest_dir))
220 return None
221 self.modules_subpaths[module_name] = subpath
222 logger.info("modules_subpath[%s] = %s" % (module_name, subpath))
223 return rev_return
224 except:
225 tag_parts.pop(0)
226 tag = '/'.join(tag_parts)
227 logger.warning("No tag matching %s" % rev)
228 return None
229
230 requiredrev = get_requiredrev(get_subpath)
231 if requiredrev:
232 logger.info("Got module(%s) requiredrev: %s" % (module_name, requiredrev))
233 if checkout:
234 subprocess.check_call('git checkout -b gomodautogen %s' % requiredrev, shell=True, cwd=repo_dest_dir)
235 self.modules_repoinfo[module_name] = (repo_url, repo_dest_dir, requiredrev)
236 else:
237 logger.warning("Failed to get requiredrev, repo_url = %s, rev = %s, module_name = %s" % (repo_url, rev, module_name))
238
239 def parse_go_mod(self, go_mod_path):
240 """
241 Parse go.mod file to get the modules info
242 """
243 # First we get the require and replace lines
244 # The parsing logic assumes the replace lines come *after* the require lines
245 inrequire = False
246 inreplace = False
247 with open(go_mod_path, 'r') as f:
248 lines = f.readlines()
249 for line in lines:
250 if line.startswith('require ('):
251 inrequire = True
252 continue
253 if line.startswith(')'):
254 inrequire = False
255 continue
256 if line.startswith('require ') or inrequire:
257 # we have one line require
258 require_line = line.lstrip('require ').split('//')[0].strip()
259 if require_line:
260 self.require_lines.append(require_line)
261 continue
262 # we can deal with requires and replaces separately because go.mod always writes requires before replaces
263 if line.startswith('replace ('):
264 inreplace = True
265 continue
266 if line.startswith(')'):
267 inreplace = False
268 continue
269 if line.startswith('replace ') or inreplace:
270 replace_line = line.lstrip('replace ').split('//')[0].strip()
271 if replace_line:
272 self.replace_lines.append(replace_line)
273 continue
274 #
275 # parse the require_lines and replace_lines to form self.modules_require and self.modules_replace
276 #
277 logger.debug("Parsing require_lines and replace_lines ...")
278 # A typical replace line is as below:
279 # github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c
280 # It means that the github.com/hashicorp/golang-lru module is replaced by github.com/ktock/golang-lru
281 # with the version 'v0.5.5-0.20211029085301-ec551be6f75c'.
282 # So the destdir is vendor/github.com/hashicorp/golang-lru while the contents are from github.com/ktock/golang-lru
283 for line in self.replace_lines:
284 orig_module, actual = line.split('=>')
285 actual_module, actual_version = actual.split()
286 orig_module = orig_module.strip()
287 actual_module = actual_module.strip()
288 actual_version = actual_version.strip()
289 self.modules_replace[orig_module] = (actual_module, actual_version)
290 #
291 # Typical require lines are as below:
292 # github.com/Masterminds/semver/v3 v3.1.1
293 # golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
294 #
295 # We need to first try https://<module_name>?=go-get=1 to see it contains
296 # line starting with '<meta name="go-import" content='.
297 #
298 # If so, get root-path vcs repo-url from content. See https://go.dev/ref/mod#vcs-find
299 # For example, the above 'wget https://golang.org/x/crypto?go-get=1' gives you
300 # <meta name="go-import" content="golang.org/x/crypto git https://go.googlesource.com/crypto">
301 # In such case, the self.modules_require has the following contents:
302 # module_name: golang.org/x/crypto
303 # url: https://go.googlesource.com/crypto
304 # version: v0.0.0-20220321153916-2c7772ba3064
305 # destdir: ${WORKDIR}/${BP}/src/import/vendor.fetch/golang.org/x/crypto
306 # fullsrcrev: 2c7772ba30643b7a2026cbea938420dce7c6384d (git rev-list -1 2c7772ba3064)
307 #
308 # If not, try https://pkg.go.dev/<module_name>, and find the 'Repository'.
309 # For example, 'wget https://pkg.go.dev/github.com/Masterminds/semver/v3' gives:
310 # github.com/Masterminds/semver
311 # In such case, the self.modules has the following contents:
312 # module_name: github.com/Masterminds/semver/v3
313 # url: https://github.com/Masterminds/semver
314 # version: v3.1.1
315 # destdir: ${WORKDIR}/${BP}/src/import/vendor.fetch/github.com/Masterminds/semver/v3
316 # fullsrcrev: 7bb0c843b53d6ad21a3f619cb22c4b442bb3ef3e (git rev-list -1 v3.1.1)
317 #
318 # As a last resort, if the last component of <module_name> matches 'v[0-9]+',
319 # remove the last component and try wget https://<module_name_with_last_component_removed>?go-get=1,
320 # then try using the above matching method.
321 #
322 for line in self.require_lines:
323 module_name, version = line.strip().split()
324 logger.debug("require line: %s" % line)
325 logger.debug("module_name = %s; version = %s" % (module_name, version))
326 # take the modules_replace into consideration to get the actual version and actual module name
327 # note that the module_name is used in destdir, and the actual_module_name and actual_version
328 # are used to determine the url and fullsrcrev
329 destdir = '${WORKDIR}/${BP}/src/import/vendor.fetch/%s' % module_name
330 actual_module_name = module_name
331 actual_version = version
332 if module_name in self.modules_replace:
333 actual_module_name, actual_version = self.modules_replace[module_name]
334 logger.debug("actual_module_name = %s; actual_version = %s" % (actual_module_name, actual_version))
335 url, fullsrcrev = self.get_url_srcrev(actual_module_name, actual_version)
336 logger.debug("url = %s; fullsrcrev = %s" % (url, fullsrcrev))
337 if url and fullsrcrev:
338 self.modules_require[module_name] = (url, version, destdir, fullsrcrev)
339 # form srcpath, actual_module_name/<subpath>
340 if actual_module_name in self.modules_subpaths:
341 subpath = self.modules_subpaths[actual_module_name]
342 srcpath = '%s/%s' % (actual_module_name, subpath)
343 self.modules_srcpaths[module_name] = srcpath
344 logger.info("self.modules_srcpaths[%s] = %s" % (module_name, srcpath))
345 else:
346 self.modules_srcpaths[module_name] = actual_module_name
347 else:
348 logger.warning("get_url_srcrev(%s, %s) failed" % (actual_module_name, actual_version))
349 if ERROR_OUT_ON_FETCH_AND_CHECKOUT_FAILURE:
350 sys.exit(1)
351
352 def use_wget_to_get_repo_url(self, wget_content_file, url_cache_file, module_name):
353 """
354 Use wget to get repo_url for module_name, return None if not found
355 """
356 try:
357 logger.info("wget -O %s https://%s?=go-get=1" % (wget_content_file, module_name))
358 subprocess.check_call('wget -O %s https://%s?=go-get=1' % (wget_content_file, module_name), shell=True)
359 with open(wget_content_file, 'r') as f:
360 for line in f.readlines():
361 if '<meta name="go-import" content=' in line:
362 logger.info("Succeed to find go-import content for %s" % module_name)
363 logger.debug("The line is %s" % line)
364 root_path, vcs, repo_url = line.split('content=')[1].split('"')[1].split()
365 logger.info("%s: %s %s %s" % (module_name, root_path, vcs, repo_url))
366 if vcs != 'git':
367 logger.warning('%s unhandled as its vcs is %s which is not supported by this script.' % (module_name, vcs))
368 unhandled_reason = 'vcs %s is not supported by this script' % vcs
369 self.modules_unhandled[module_name] = unhandled_reason
370 return None
371 with open(url_cache_file, 'w') as f:
372 f.write(repo_url)
373 return repo_url
374 except:
375 logger.info("wget -O %s https://%s?=go-get=1 failed" % (wget_content_file, module_name))
376 # if we cannot find repo url from https://<module_name>?=go-get=1, try https://pkg.go/dev/<module_name>
377 try:
378 logger.info("wget -O %s https://pkg.go.dev/%s" % (wget_content_file, module_name))
379 subprocess.check_call("wget -O %s https://pkg.go.dev/%s" % (wget_content_file, module_name), shell=True)
380 repo_url_found = False
381 with open(wget_content_file, 'r') as f:
382 in_repo_section = False
383 for line in f.readlines():
384 if '>Repository<' in line:
385 in_repo_section = True
386 continue
387 if in_repo_section:
388 newline = line.strip()
389 if newline != '' and not newline.startswith('<'):
390 repo_url = newline
391 repo_url_found = True
392 break
393 if repo_url_found:
394 logger.info("repo url for %s: %s" % (module_name, repo_url))
395 with open(url_cache_file, 'w') as f:
396 f.write(repo_url)
397 return repo_url
398 else:
399 unhandled_reason = 'cannot determine repo_url for %s' % module_name
400 self.modules_unhandled[module_name] = unhandled_reason
401 return None
402 except:
403 logger.info("wget -O %s https://pkg.go.dev/%s failed" % (wget_content_file, module_name))
404 return None
405
406
407 def get_repo_url_rev(self, module_name, version):
408 """
409 Return (repo_url, rev)
410 """
411 import re
412 # First get rev from version
413 v = version.split('+incompatible')[0]
414 version_components = v.split('-')
415 if len(version_components) == 1:
416 rev = v
417 elif len(version_components) == 3:
418 if len(version_components[2]) == 12:
419 rev = version_components[2]
420 else:
421 rev = v
422 else:
423 rev = v
424
425 #
426 # Get repo_url
427 # We put a cache mechanism here, <wget_content_file>.repo_url.cache is used to store the repo url fetch before
428 #
429 wget_dir = os.path.join(self.workdir, 'wget-contents')
430 if not os.path.exists(wget_dir):
431 os.makedirs(wget_dir)
432 wget_content_file = os.path.join(wget_dir, module_name.replace('/', '_'))
433 url_cache_file = "%s.repo_url.cache" % wget_content_file
434 if os.path.exists(url_cache_file):
435 with open(url_cache_file, 'r') as f:
436 repo_url = f.readline().strip()
437 return (repo_url, rev)
438 module_name_parts = module_name.split('/')
439 while (len(module_name_parts) > 0):
440 module_name_to_check = '/'.join(module_name_parts)
441 logger.info("module_name_to_check: %s" % module_name_to_check)
442 repo_url = self.use_wget_to_get_repo_url(wget_content_file, url_cache_file, module_name_to_check)
443 if repo_url:
444 return (repo_url, rev)
445 else:
446 if module_name in self.modules_unhandled:
447 return (None, rev)
448 else:
449 module_name_parts.pop(-1)
450
451 unhandled_reason = 'cannot determine the repo for %s' % module_name
452 self.modules_unhandled[module_name] = unhandled_reason
453 return (None, rev)
454
455 def get_url_srcrev(self, module_name, version):
456 """
457 Return url and fullsrcrev according to module_name and version
458 """
459 repo_url, rev = self.get_repo_url_rev(module_name, version)
460 if not repo_url or not rev:
461 return (None, None)
462 self.fetch_and_checkout_repo(module_name, repo_url, rev)
463 if module_name in self.modules_repoinfo:
464 repo_url, repo_dest_dir, repo_fullrev = self.modules_repoinfo[module_name]
465 # remove the .git suffix to sync repos across modules with different versions and across recipes
466 if repo_url.endswith('.git'):
467 repo_url = repo_url[:-len('.git')]
468 return (repo_url, repo_fullrev)
469 else:
470 unhandled_reason = 'fetch_and_checkout_repo(%s, %s, %s) failed' % (module_name, repo_url, rev)
471 self.modules_unhandled[module_name] = unhandled_reason
472 return (None, None)
473
474 def gen_src_uri_inc(self):
475 """
476 Generate src_uri.inc file containing SRC_URIs
477 """
478 src_uri_inc_file = os.path.join(self.workdir, 'src_uri.inc')
479 # record the <name> after writting SRCREV_<name>, this is to avoid modules having the same basename resulting in same SRCREV_xxx
480 srcrev_name_recorded = []
481 template = """# %s %s
482# [1] git ls-remote %s %s
483SRCREV_%s="%s"
484SRC_URI += "git://%s;name=%s;protocol=https;nobranch=1;destsuffix=${WORKDIR}/${BP}/src/import/vendor.fetch/%s"
485
486"""
487 # We can't simply write SRC_URIs one by one in the order that go.mod specify them.
488 # Because the latter one might clean things up for the former one if the former one is a subpath of the latter one.
489 def take_first_len(elem):
490 return len(elem[0])
491
492 src_uri_contents = []
493 with open(src_uri_inc_file, 'w') as f:
494 for module in self.modules_require:
495 # {module_name: (url, version, destdir, fullsrcrev)}
496 repo_url, version, destdir, fullrev = self.modules_require[module]
497 if module in self.modules_replace:
498 actual_module_name, actual_version = self.modules_replace[module]
499 else:
500 actual_module_name, actual_version = (module, version)
501 if '://' in repo_url:
502 repo_url_noprotocol = repo_url.split('://')[1]
503 else:
504 repo_url_noprotocol = repo_url
505 if not repo_url.startswith('https://'):
506 repo_url = 'https://' + repo_url
507 name = module.split('/')[-1]
508 if name in srcrev_name_recorded:
509 name = '-'.join(module.split('/')[-2:])
510 src_uri_contents.append((actual_module_name, actual_version, repo_url, fullrev, name, fullrev, repo_url_noprotocol, name, actual_module_name))
511 srcrev_name_recorded.append(name)
512 # sort the src_uri_contents and then write it
513 src_uri_contents.sort(key=take_first_len)
514 for content in src_uri_contents:
515 f.write(template % content)
516 logger.info("%s generated" % src_uri_inc_file)
517
518 def gen_relocation_inc(self):
519 """
520 Generate relocation.inc file
521 """
522 relocation_inc_file = os.path.join(self.workdir, 'relocation.inc')
523 template = """export sites="%s"
524
525do_compile:prepend() {
526 cd ${S}/src/import
527 for s in $sites; do
528 site_dest=$(echo $s | cut -d: -f1)
529 site_source=$(echo $s | cut -d: -f2)
530 force_flag=$(echo $s | cut -d: -f3)
531 mkdir -p vendor.copy/$site_dest
532 if [ -n "$force_flag" ]; then
533 echo "[INFO] $site_dest: force copying .go files"
534 rm -rf vendor.copy/$site_dest
535 rsync -a --exclude='vendor/' --exclude='.git/' vendor.fetch/$site_source/ vendor.copy/$site_dest
536 else
537 [ -n "$(ls -A vendor.copy/$site_dest/*.go 2> /dev/null)" ] && { echo "[INFO] vendor.fetch/$site_source -> $site_dest: go copy skipped (files present)" ; true ; } || { echo "[INFO] $site_dest: copying .go files" ; rsync -a --exclude='vendor/' --exclude='.git/' vendor.fetch/$site_source/ vendor.copy/$site_dest ; }
538 fi
539 done
540}
541"""
542 sites = []
543 for module in self.modules_require:
544 # <dest>:<source>[:force]
545 if module in self.modules_srcpaths:
546 srcpath = self.modules_srcpaths[module]
547 logger.debug("Using %s as srcpath of module (%s)" % (srcpath, module))
548 else:
549 srcpath = module
550 sites.append("%s:%s:force" % (module, srcpath))
551 # To avoid the former one being overriden by the latter one when the former one is a subpath of the latter one, sort sites
552 sites.sort(key=len)
553 with open(relocation_inc_file, 'w') as f:
554 sites_str = ' \\\n '.join(sites)
555 f.write(template % sites_str)
556 logger.info("%s generated" % relocation_inc_file)
557
558 def gen_modules_txt(self):
559 """
560 Generate modules.txt file
561 """
562 modules_txt_file = os.path.join(self.workdir, 'modules.txt')
563 with open(modules_txt_file, 'w') as f:
564 for l in self.require_lines:
565 f.write('# %s\n' % l)
566 f.write('## explicit\n')
567 for l in self.replace_lines:
568 f.write('# %s\n' %l)
569 logger.info("%s generated" % modules_txt_file)
570
571 def sanity_check(self):
572 """
573 Various anity checks
574 """
575 sanity_check_ok = True
576 #
577 # Sanity Check 1:
578 # For modules having the same repo, at most one is allowed to not have subpath.
579 # This check operates on self.modules_repoinfo and self.modules_subpaths
580 #
581 repo_modules = {}
582 for module in self.modules_repoinfo:
583 # first form {repo: [module1, module2, ...]}
584 repo_url, repo_dest_dir, fullsrcrev = self.modules_repoinfo[module]
585 if repo_url not in repo_modules:
586 repo_modules[repo_url] = [module]
587 else:
588 repo_modules[repo_url].append(module)
589 for repo in repo_modules:
590 modules = repo_modules[repo]
591 if len(modules) == 1:
592 continue
593 # for modules sharing the same repo, at most one is allowed to not have subpath
594 nosubpath_modules = []
595 for m in modules:
596 if m not in self.modules_subpaths:
597 nosubpath_modules.append(m)
598 if len(nosubpath_modules) == 0:
599 continue
600 if len(nosubpath_modules) > 1:
601 logger.warning("Multiple modules sharing %s, but they don't have subpath: %s. Please double check." % (repo, nosubpath_modules))
602 if len(nosubpath_modules) == 1:
603 # do further check, OK if the module is the prefix for other modules sharing the same repo
604 module_to_check = nosubpath_modules[0]
605 for m in modules:
606 if module_to_check == m:
607 continue
608 if not m.startswith('%s/' % module_to_check):
609 logger.warning("%s is sharing repo (%s) with other modules, and it might need a subpath. Please double check" % (module_to_check, repo))
610 continue
611
612 #
613 # End of Sanity Check
614 #
615 if not sanity_check_ok:
616 sys.exit(1)
617 return
618
619def main():
620 parser = argparse.ArgumentParser(
621 description="oe-go-mod-autogen.py is used to generate src_uri.inc, relocation.inc and modules.txt to be used by go mod recipes",
622 epilog="Use %(prog)s --help to get help")
623 parser.add_argument("--repo", help = "Repo for the recipe.", required=True)
624 parser.add_argument("--rev", help = "Revision for the recipe.", required=True)
625 parser.add_argument("--module", help = "Go module name. To be used with '--test'")
626 parser.add_argument("--version", help = "Go module version. To be used with '--test'")
627 parser.add_argument("--test", help = "Test to get repo url and fullsrcrev, used together with --module and --version.", action="store_true")
628 parser.add_argument("--workdir", help = "Working directory to hold intermediate results and output.", default=os.getcwd())
629 parser.add_argument("-d", "--debug",
630 help = "Enable debug output",
631 action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
632 parser.add_argument("-q", "--quiet",
633 help = "Hide all output except error messages",
634 action="store_const", const=logging.ERROR, dest="loglevel")
635 args = parser.parse_args()
636
637 logger.setLevel(args.loglevel)
638 logger.debug("oe-go-mod-autogen.py running for %s:%s in %s" % (args.repo, args.rev, args.workdir))
639 gomodtool = GoModTool(args.repo, args.rev, args.workdir)
640 if args.test:
641 if not args.module or not args.version:
642 print("Please specify --module and --version")
643 sys.exit(1)
644 url, srcrev = gomodtool.get_url_srcrev(args.module, args.version)
645 print("url = %s, srcrev = %s" % (url, srcrev))
646 if not url or not srcrev:
647 print("Failed to get url & srcrev for %s:%s" % (args.module, args.version))
648 else:
649 gomodtool.parse()
650 gomodtool.sanity_check()
651 gomodtool.gen_src_uri_inc()
652 gomodtool.gen_relocation_inc()
653 gomodtool.gen_modules_txt()
654
655
656if __name__ == "__main__":
657 try:
658 ret = main()
659 except Exception as esc:
660 ret = 1
661 import traceback
662 traceback.print_exc()
663 sys.exit(ret)