summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/fetch2
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/fetch2')
-rw-r--r--bitbake/lib/bb/fetch2/__init__.py224
-rw-r--r--bitbake/lib/bb/fetch2/az.py9
-rw-r--r--bitbake/lib/bb/fetch2/clearcase.py6
-rw-r--r--bitbake/lib/bb/fetch2/crate.py9
-rw-r--r--bitbake/lib/bb/fetch2/gcp.py15
-rw-r--r--bitbake/lib/bb/fetch2/git.py459
-rw-r--r--bitbake/lib/bb/fetch2/gitsm.py126
-rw-r--r--bitbake/lib/bb/fetch2/gomod.py273
-rw-r--r--bitbake/lib/bb/fetch2/local.py9
-rw-r--r--bitbake/lib/bb/fetch2/npm.py24
-rw-r--r--bitbake/lib/bb/fetch2/npmsw.py98
-rw-r--r--bitbake/lib/bb/fetch2/s3.py2
-rw-r--r--bitbake/lib/bb/fetch2/sftp.py2
-rw-r--r--bitbake/lib/bb/fetch2/ssh.py3
-rw-r--r--bitbake/lib/bb/fetch2/svn.py3
-rw-r--r--bitbake/lib/bb/fetch2/wget.py108
16 files changed, 888 insertions, 482 deletions
diff --git a/bitbake/lib/bb/fetch2/__init__.py b/bitbake/lib/bb/fetch2/__init__.py
index 5bf2c4b8cf..0ad987c596 100644
--- a/bitbake/lib/bb/fetch2/__init__.py
+++ b/bitbake/lib/bb/fetch2/__init__.py
@@ -23,17 +23,18 @@ import collections
23import subprocess 23import subprocess
24import pickle 24import pickle
25import errno 25import errno
26import bb.persist_data, bb.utils 26import bb.utils
27import bb.checksum 27import bb.checksum
28import bb.process 28import bb.process
29import bb.event 29import bb.event
30 30
31__version__ = "2" 31__version__ = "2"
32_checksum_cache = bb.checksum.FileChecksumCache() 32_checksum_cache = bb.checksum.FileChecksumCache()
33_revisions_cache = bb.checksum.RevisionsCache()
33 34
34logger = logging.getLogger("BitBake.Fetcher") 35logger = logging.getLogger("BitBake.Fetcher")
35 36
36CHECKSUM_LIST = [ "md5", "sha256", "sha1", "sha384", "sha512" ] 37CHECKSUM_LIST = [ "goh1", "md5", "sha256", "sha1", "sha384", "sha512" ]
37SHOWN_CHECKSUM_LIST = ["sha256"] 38SHOWN_CHECKSUM_LIST = ["sha256"]
38 39
39class BBFetchException(Exception): 40class BBFetchException(Exception):
@@ -237,7 +238,7 @@ class URI(object):
237 # to RFC compliant URL format. E.g.: 238 # to RFC compliant URL format. E.g.:
238 # file://foo.diff -> file:foo.diff 239 # file://foo.diff -> file:foo.diff
239 if urlp.scheme in self._netloc_forbidden: 240 if urlp.scheme in self._netloc_forbidden:
240 uri = re.sub("(?<=:)//(?!/)", "", uri, 1) 241 uri = re.sub(r"(?<=:)//(?!/)", "", uri, count=1)
241 reparse = 1 242 reparse = 1
242 243
243 if reparse: 244 if reparse:
@@ -352,6 +353,14 @@ def decodeurl(url):
352 user, password, parameters). 353 user, password, parameters).
353 """ 354 """
354 355
356 uri = URI(url)
357 path = uri.path if uri.path else "/"
358 return uri.scheme, uri.hostport, path, uri.username, uri.password, uri.params
359
360def decodemirrorurl(url):
361 """Decodes a mirror URL into the tokens (scheme, network location, path,
362 user, password, parameters).
363 """
355 m = re.compile('(?P<type>[^:]*)://((?P<user>[^/;]+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url) 364 m = re.compile('(?P<type>[^:]*)://((?P<user>[^/;]+)@)?(?P<location>[^;]+)(;(?P<parm>.*))?').match(url)
356 if not m: 365 if not m:
357 raise MalformedUrl(url) 366 raise MalformedUrl(url)
@@ -370,6 +379,9 @@ def decodeurl(url):
370 elif type.lower() == 'file': 379 elif type.lower() == 'file':
371 host = "" 380 host = ""
372 path = location 381 path = location
382 if user:
383 path = user + '@' + path
384 user = ""
373 else: 385 else:
374 host = location 386 host = location
375 path = "/" 387 path = "/"
@@ -402,32 +414,34 @@ def encodeurl(decoded):
402 414
403 if not type: 415 if not type:
404 raise MissingParameterError('type', "encoded from the data %s" % str(decoded)) 416 raise MissingParameterError('type', "encoded from the data %s" % str(decoded))
405 url = ['%s://' % type] 417 uri = URI()
418 uri.scheme = type
406 if user and type != "file": 419 if user and type != "file":
407 url.append("%s" % user) 420 uri.username = user
408 if pswd: 421 if pswd:
409 url.append(":%s" % pswd) 422 uri.password = pswd
410 url.append("@")
411 if host and type != "file": 423 if host and type != "file":
412 url.append("%s" % host) 424 uri.hostname = host
413 if path: 425 if path:
414 # Standardise path to ensure comparisons work 426 # Standardise path to ensure comparisons work
415 while '//' in path: 427 while '//' in path:
416 path = path.replace("//", "/") 428 path = path.replace("//", "/")
417 url.append("%s" % urllib.parse.quote(path)) 429 uri.path = path
430 if type == "file":
431 # Use old not IETF compliant style
432 uri.relative = False
418 if p: 433 if p:
419 for parm in p: 434 uri.params = p
420 url.append(";%s=%s" % (parm, p[parm]))
421 435
422 return "".join(url) 436 return str(uri)
423 437
424def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None): 438def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
425 if not ud.url or not uri_find or not uri_replace: 439 if not ud.url or not uri_find or not uri_replace:
426 logger.error("uri_replace: passed an undefined value, not replacing") 440 logger.error("uri_replace: passed an undefined value, not replacing")
427 return None 441 return None
428 uri_decoded = list(decodeurl(ud.url)) 442 uri_decoded = list(decodemirrorurl(ud.url))
429 uri_find_decoded = list(decodeurl(uri_find)) 443 uri_find_decoded = list(decodemirrorurl(uri_find))
430 uri_replace_decoded = list(decodeurl(uri_replace)) 444 uri_replace_decoded = list(decodemirrorurl(uri_replace))
431 logger.debug2("For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded)) 445 logger.debug2("For url %s comparing %s to %s" % (uri_decoded, uri_find_decoded, uri_replace_decoded))
432 result_decoded = ['', '', '', '', '', {}] 446 result_decoded = ['', '', '', '', '', {}]
433 # 0 - type, 1 - host, 2 - path, 3 - user, 4- pswd, 5 - params 447 # 0 - type, 1 - host, 2 - path, 3 - user, 4- pswd, 5 - params
@@ -460,7 +474,7 @@ def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None):
460 for k in replacements: 474 for k in replacements:
461 uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k]) 475 uri_replace_decoded[loc] = uri_replace_decoded[loc].replace(k, replacements[k])
462 #bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc])) 476 #bb.note("%s %s %s" % (regexp, uri_replace_decoded[loc], uri_decoded[loc]))
463 result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc], 1) 477 result_decoded[loc] = re.sub(regexp, uri_replace_decoded[loc], uri_decoded[loc], count=1)
464 if loc == 2: 478 if loc == 2:
465 # Handle path manipulations 479 # Handle path manipulations
466 basename = None 480 basename = None
@@ -493,18 +507,23 @@ methods = []
493urldata_cache = {} 507urldata_cache = {}
494saved_headrevs = {} 508saved_headrevs = {}
495 509
496def fetcher_init(d): 510def fetcher_init(d, servercontext=True):
497 """ 511 """
498 Called to initialize the fetchers once the configuration data is known. 512 Called to initialize the fetchers once the configuration data is known.
499 Calls before this must not hit the cache. 513 Calls before this must not hit the cache.
500 """ 514 """
501 515
502 revs = bb.persist_data.persist('BB_URI_HEADREVS', d) 516 _checksum_cache.init_cache(d.getVar("BB_CACHEDIR"))
517 _revisions_cache.init_cache(d.getVar("BB_CACHEDIR"))
518
519 if not servercontext:
520 return
521
503 try: 522 try:
504 # fetcher_init is called multiple times, so make sure we only save the 523 # fetcher_init is called multiple times, so make sure we only save the
505 # revs the first time it is called. 524 # revs the first time it is called.
506 if not bb.fetch2.saved_headrevs: 525 if not bb.fetch2.saved_headrevs:
507 bb.fetch2.saved_headrevs = dict(revs) 526 bb.fetch2.saved_headrevs = _revisions_cache.get_revs()
508 except: 527 except:
509 pass 528 pass
510 529
@@ -514,11 +533,10 @@ def fetcher_init(d):
514 logger.debug("Keeping SRCREV cache due to cache policy of: %s", srcrev_policy) 533 logger.debug("Keeping SRCREV cache due to cache policy of: %s", srcrev_policy)
515 elif srcrev_policy == "clear": 534 elif srcrev_policy == "clear":
516 logger.debug("Clearing SRCREV cache due to cache policy of: %s", srcrev_policy) 535 logger.debug("Clearing SRCREV cache due to cache policy of: %s", srcrev_policy)
517 revs.clear() 536 _revisions_cache.clear_cache()
518 else: 537 else:
519 raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy) 538 raise FetchError("Invalid SRCREV cache policy of: %s" % srcrev_policy)
520 539
521 _checksum_cache.init_cache(d.getVar("BB_CACHEDIR"))
522 540
523 for m in methods: 541 for m in methods:
524 if hasattr(m, "init"): 542 if hasattr(m, "init"):
@@ -526,9 +544,11 @@ def fetcher_init(d):
526 544
527def fetcher_parse_save(): 545def fetcher_parse_save():
528 _checksum_cache.save_extras() 546 _checksum_cache.save_extras()
547 _revisions_cache.save_extras()
529 548
530def fetcher_parse_done(): 549def fetcher_parse_done():
531 _checksum_cache.save_merge() 550 _checksum_cache.save_merge()
551 _revisions_cache.save_merge()
532 552
533def fetcher_compare_revisions(d): 553def fetcher_compare_revisions(d):
534 """ 554 """
@@ -536,7 +556,7 @@ def fetcher_compare_revisions(d):
536 when bitbake was started and return true if they have changed. 556 when bitbake was started and return true if they have changed.
537 """ 557 """
538 558
539 headrevs = dict(bb.persist_data.persist('BB_URI_HEADREVS', d)) 559 headrevs = _revisions_cache.get_revs()
540 return headrevs != bb.fetch2.saved_headrevs 560 return headrevs != bb.fetch2.saved_headrevs
541 561
542def mirror_from_string(data): 562def mirror_from_string(data):
@@ -786,8 +806,8 @@ def _get_srcrev(d, method_name='sortable_revision'):
786 return "", revs 806 return "", revs
787 807
788 808
789 if len(scms) == 1 and len(urldata[scms[0]].names) == 1: 809 if len(scms) == 1:
790 autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].names[0]) 810 autoinc, rev = getattr(urldata[scms[0]].method, method_name)(urldata[scms[0]], d, urldata[scms[0]].name)
791 revs.append(rev) 811 revs.append(rev)
792 if len(rev) > 10: 812 if len(rev) > 10:
793 rev = rev[:10] 813 rev = rev[:10]
@@ -808,13 +828,12 @@ def _get_srcrev(d, method_name='sortable_revision'):
808 seenautoinc = False 828 seenautoinc = False
809 for scm in scms: 829 for scm in scms:
810 ud = urldata[scm] 830 ud = urldata[scm]
811 for name in ud.names: 831 autoinc, rev = getattr(ud.method, method_name)(ud, d, ud.name)
812 autoinc, rev = getattr(ud.method, method_name)(ud, d, name) 832 revs.append(rev)
813 revs.append(rev) 833 seenautoinc = seenautoinc or autoinc
814 seenautoinc = seenautoinc or autoinc 834 if len(rev) > 10:
815 if len(rev) > 10: 835 rev = rev[:10]
816 rev = rev[:10] 836 name_to_rev[ud.name] = rev
817 name_to_rev[name] = rev
818 # Replace names by revisions in the SRCREV_FORMAT string. The approach used 837 # Replace names by revisions in the SRCREV_FORMAT string. The approach used
819 # here can handle names being prefixes of other names and names appearing 838 # here can handle names being prefixes of other names and names appearing
820 # as substrings in revisions (in which case the name should not be 839 # as substrings in revisions (in which case the name should not be
@@ -878,6 +897,7 @@ FETCH_EXPORT_VARS = ['HOME', 'PATH',
878 'AWS_SESSION_TOKEN', 897 'AWS_SESSION_TOKEN',
879 'GIT_CACHE_PATH', 898 'GIT_CACHE_PATH',
880 'REMOTE_CONTAINERS_IPC', 899 'REMOTE_CONTAINERS_IPC',
900 'GITHUB_TOKEN',
881 'SSL_CERT_DIR'] 901 'SSL_CERT_DIR']
882 902
883def get_fetcher_environment(d): 903def get_fetcher_environment(d):
@@ -1072,6 +1092,10 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
1072 # If that tarball is a local file:// we need to provide a symlink to it 1092 # If that tarball is a local file:// we need to provide a symlink to it
1073 dldir = ld.getVar("DL_DIR") 1093 dldir = ld.getVar("DL_DIR")
1074 1094
1095 if bb.utils.to_boolean(ld.getVar("BB_FETCH_PREMIRRORONLY")):
1096 ld = ld.createCopy()
1097 ld.setVar("BB_NO_NETWORK", "1")
1098
1075 if origud.mirrortarballs and os.path.basename(ud.localpath) in origud.mirrortarballs and os.path.basename(ud.localpath) != os.path.basename(origud.localpath): 1099 if origud.mirrortarballs and os.path.basename(ud.localpath) in origud.mirrortarballs and os.path.basename(ud.localpath) != os.path.basename(origud.localpath):
1076 # Create donestamp in old format to avoid triggering a re-download 1100 # Create donestamp in old format to avoid triggering a re-download
1077 if ud.donestamp: 1101 if ud.donestamp:
@@ -1093,7 +1117,10 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
1093 origud.method.build_mirror_data(origud, ld) 1117 origud.method.build_mirror_data(origud, ld)
1094 return origud.localpath 1118 return origud.localpath
1095 # Otherwise the result is a local file:// and we symlink to it 1119 # Otherwise the result is a local file:// and we symlink to it
1096 ensure_symlink(ud.localpath, origud.localpath) 1120 # This may also be a link to a shallow archive
1121 # When using shallow mode, add a symlink to the original fullshallow
1122 # path to ensure a valid symlink even in the `PREMIRRORS` case
1123 origud.method.update_mirror_links(ud, origud)
1097 update_stamp(origud, ld) 1124 update_stamp(origud, ld)
1098 return ud.localpath 1125 return ud.localpath
1099 1126
@@ -1127,25 +1154,6 @@ def try_mirror_url(fetch, origud, ud, ld, check = False):
1127 if ud.lockfile and ud.lockfile != origud.lockfile: 1154 if ud.lockfile and ud.lockfile != origud.lockfile:
1128 bb.utils.unlockfile(lf) 1155 bb.utils.unlockfile(lf)
1129 1156
1130
1131def ensure_symlink(target, link_name):
1132 if not os.path.exists(link_name):
1133 dirname = os.path.dirname(link_name)
1134 bb.utils.mkdirhier(dirname)
1135 if os.path.islink(link_name):
1136 # Broken symbolic link
1137 os.unlink(link_name)
1138
1139 # In case this is executing without any file locks held (as is
1140 # the case for file:// URLs), two tasks may end up here at the
1141 # same time, in which case we do not want the second task to
1142 # fail when the link has already been created by the first task.
1143 try:
1144 os.symlink(target, link_name)
1145 except FileExistsError:
1146 pass
1147
1148
1149def try_mirrors(fetch, d, origud, mirrors, check = False): 1157def try_mirrors(fetch, d, origud, mirrors, check = False):
1150 """ 1158 """
1151 Try to use a mirrored version of the sources. 1159 Try to use a mirrored version of the sources.
@@ -1174,7 +1182,7 @@ def trusted_network(d, url):
1174 if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")): 1182 if bb.utils.to_boolean(d.getVar("BB_NO_NETWORK")):
1175 return True 1183 return True
1176 1184
1177 pkgname = d.expand(d.getVar('PN', False)) 1185 pkgname = d.getVar('PN')
1178 trusted_hosts = None 1186 trusted_hosts = None
1179 if pkgname: 1187 if pkgname:
1180 trusted_hosts = d.getVarFlag('BB_ALLOWED_NETWORKS', pkgname, False) 1188 trusted_hosts = d.getVarFlag('BB_ALLOWED_NETWORKS', pkgname, False)
@@ -1227,20 +1235,17 @@ def srcrev_internal_helper(ud, d, name):
1227 if srcrev and srcrev != "INVALID": 1235 if srcrev and srcrev != "INVALID":
1228 break 1236 break
1229 1237
1230 if 'rev' in ud.parm and 'tag' in ud.parm: 1238 if 'rev' in ud.parm:
1231 raise FetchError("Please specify a ;rev= parameter or a ;tag= parameter in the url %s but not both." % (ud.url)) 1239 parmrev = ud.parm['rev']
1232
1233 if 'rev' in ud.parm or 'tag' in ud.parm:
1234 if 'rev' in ud.parm:
1235 parmrev = ud.parm['rev']
1236 else:
1237 parmrev = ud.parm['tag']
1238 if srcrev == "INVALID" or not srcrev: 1240 if srcrev == "INVALID" or not srcrev:
1239 return parmrev 1241 return parmrev
1240 if srcrev != parmrev: 1242 if srcrev != parmrev:
1241 raise FetchError("Conflicting revisions (%s from SRCREV and %s from the url) found, please specify one valid value" % (srcrev, parmrev)) 1243 raise FetchError("Conflicting revisions (%s from SRCREV and %s from the url) found, please specify one valid value" % (srcrev, parmrev))
1242 return parmrev 1244 return parmrev
1243 1245
1246 if 'tag' in ud.parm and (srcrev == "INVALID" or not srcrev):
1247 return ud.parm['tag']
1248
1244 if srcrev == "INVALID" or not srcrev: 1249 if srcrev == "INVALID" or not srcrev:
1245 raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url) 1250 raise FetchError("Please set a valid SRCREV for url %s (possible key names are %s, or use a ;rev=X URL parameter)" % (str(attempts), ud.url), ud.url)
1246 if srcrev == "AUTOINC": 1251 if srcrev == "AUTOINC":
@@ -1263,7 +1268,7 @@ def get_checksum_file_list(d):
1263 found = False 1268 found = False
1264 paths = ud.method.localfile_searchpaths(ud, d) 1269 paths = ud.method.localfile_searchpaths(ud, d)
1265 for f in paths: 1270 for f in paths:
1266 pth = ud.decodedurl 1271 pth = ud.path
1267 if os.path.exists(f): 1272 if os.path.exists(f):
1268 found = True 1273 found = True
1269 filelist.append(f + ":" + str(os.path.exists(f))) 1274 filelist.append(f + ":" + str(os.path.exists(f)))
@@ -1308,23 +1313,28 @@ class FetchData(object):
1308 self.setup = False 1313 self.setup = False
1309 1314
1310 def configure_checksum(checksum_id): 1315 def configure_checksum(checksum_id):
1316 checksum_plain_name = "%ssum" % checksum_id
1311 if "name" in self.parm: 1317 if "name" in self.parm:
1312 checksum_name = "%s.%ssum" % (self.parm["name"], checksum_id) 1318 checksum_name = "%s.%ssum" % (self.parm["name"], checksum_id)
1313 else: 1319 else:
1314 checksum_name = "%ssum" % checksum_id 1320 checksum_name = checksum_plain_name
1315
1316 setattr(self, "%s_name" % checksum_id, checksum_name)
1317 1321
1318 if checksum_name in self.parm: 1322 if checksum_name in self.parm:
1319 checksum_expected = self.parm[checksum_name] 1323 checksum_expected = self.parm[checksum_name]
1320 elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs"]: 1324 elif checksum_plain_name in self.parm:
1325 checksum_expected = self.parm[checksum_plain_name]
1326 checksum_name = checksum_plain_name
1327 elif self.type not in ["http", "https", "ftp", "ftps", "sftp", "s3", "az", "crate", "gs", "gomod", "npm"]:
1321 checksum_expected = None 1328 checksum_expected = None
1322 else: 1329 else:
1323 checksum_expected = d.getVarFlag("SRC_URI", checksum_name) 1330 checksum_expected = d.getVarFlag("SRC_URI", checksum_name)
1324 1331
1332 setattr(self, "%s_name" % checksum_id, checksum_name)
1325 setattr(self, "%s_expected" % checksum_id, checksum_expected) 1333 setattr(self, "%s_expected" % checksum_id, checksum_expected)
1326 1334
1327 self.names = self.parm.get("name",'default').split(',') 1335 self.name = self.parm.get("name",'default')
1336 if "," in self.name:
1337 raise ParameterError("The fetcher no longer supports multiple name parameters in a single url", self.url)
1328 1338
1329 self.method = None 1339 self.method = None
1330 for m in methods: 1340 for m in methods:
@@ -1376,13 +1386,7 @@ class FetchData(object):
1376 self.lockfile = basepath + '.lock' 1386 self.lockfile = basepath + '.lock'
1377 1387
1378 def setup_revisions(self, d): 1388 def setup_revisions(self, d):
1379 self.revisions = {} 1389 self.revision = srcrev_internal_helper(self, d, self.name)
1380 for name in self.names:
1381 self.revisions[name] = srcrev_internal_helper(self, d, name)
1382
1383 # add compatibility code for non name specified case
1384 if len(self.names) == 1:
1385 self.revision = self.revisions[self.names[0]]
1386 1390
1387 def setup_localpath(self, d): 1391 def setup_localpath(self, d):
1388 if not self.localpath: 1392 if not self.localpath:
@@ -1510,7 +1514,7 @@ class FetchMethod(object):
1510 (file, urldata.parm.get('unpack'))) 1514 (file, urldata.parm.get('unpack')))
1511 1515
1512 base, ext = os.path.splitext(file) 1516 base, ext = os.path.splitext(file)
1513 if ext in ['.gz', '.bz2', '.Z', '.xz', '.lz']: 1517 if ext in ['.gz', '.bz2', '.Z', '.xz', '.lz', '.zst']:
1514 efile = os.path.join(rootdir, os.path.basename(base)) 1518 efile = os.path.join(rootdir, os.path.basename(base))
1515 else: 1519 else:
1516 efile = file 1520 efile = file
@@ -1569,11 +1573,11 @@ class FetchMethod(object):
1569 datafile = None 1573 datafile = None
1570 if output: 1574 if output:
1571 for line in output.decode().splitlines(): 1575 for line in output.decode().splitlines():
1572 if line.startswith('data.tar.'): 1576 if line.startswith('data.tar.') or line == 'data.tar':
1573 datafile = line 1577 datafile = line
1574 break 1578 break
1575 else: 1579 else:
1576 raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar.* file", urldata.url) 1580 raise UnpackError("Unable to unpack deb/ipk package - does not contain data.tar* file", urldata.url)
1577 else: 1581 else:
1578 raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url) 1582 raise UnpackError("Unable to unpack deb/ipk package - could not list contents", urldata.url)
1579 cmd = 'ar x %s %s && %s -p -f %s && rm %s' % (file, datafile, tar_cmd, datafile, datafile) 1583 cmd = 'ar x %s %s && %s -p -f %s && rm %s' % (file, datafile, tar_cmd, datafile, datafile)
@@ -1606,7 +1610,7 @@ class FetchMethod(object):
1606 if urlpath.find("/") != -1: 1610 if urlpath.find("/") != -1:
1607 destdir = urlpath.rsplit("/", 1)[0] + '/' 1611 destdir = urlpath.rsplit("/", 1)[0] + '/'
1608 bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir)) 1612 bb.utils.mkdirhier("%s/%s" % (unpackdir, destdir))
1609 cmd = 'cp -fpPRH "%s" "%s"' % (file, destdir) 1613 cmd = 'cp --force --preserve=timestamps --no-dereference --recursive -H "%s" "%s"' % (file, destdir)
1610 else: 1614 else:
1611 urldata.unpack_tracer.unpack("archive-extract", unpackdir) 1615 urldata.unpack_tracer.unpack("archive-extract", unpackdir)
1612 1616
@@ -1635,6 +1639,28 @@ class FetchMethod(object):
1635 """ 1639 """
1636 bb.utils.remove(urldata.localpath) 1640 bb.utils.remove(urldata.localpath)
1637 1641
1642 def ensure_symlink(self, target, link_name):
1643 if not os.path.exists(link_name):
1644 dirname = os.path.dirname(link_name)
1645 bb.utils.mkdirhier(dirname)
1646 if os.path.islink(link_name):
1647 # Broken symbolic link
1648 os.unlink(link_name)
1649
1650 # In case this is executing without any file locks held (as is
1651 # the case for file:// URLs), two tasks may end up here at the
1652 # same time, in which case we do not want the second task to
1653 # fail when the link has already been created by the first task.
1654 try:
1655 os.symlink(target, link_name)
1656 except FileExistsError:
1657 pass
1658
1659 def update_mirror_links(self, ud, origud):
1660 # For local file:// results, create a symlink to them
1661 # This may also be a link to a shallow archive
1662 self.ensure_symlink(ud.localpath, origud.localpath)
1663
1638 def try_premirror(self, urldata, d): 1664 def try_premirror(self, urldata, d):
1639 """ 1665 """
1640 Should premirrors be used? 1666 Should premirrors be used?
@@ -1662,13 +1688,13 @@ class FetchMethod(object):
1662 if not hasattr(self, "_latest_revision"): 1688 if not hasattr(self, "_latest_revision"):
1663 raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url) 1689 raise ParameterError("The fetcher for this URL does not support _latest_revision", ud.url)
1664 1690
1665 revs = bb.persist_data.persist('BB_URI_HEADREVS', d)
1666 key = self.generate_revision_key(ud, d, name) 1691 key = self.generate_revision_key(ud, d, name)
1667 try: 1692
1668 return revs[key] 1693 rev = _revisions_cache.get_rev(key)
1669 except KeyError: 1694 if rev is None:
1670 revs[key] = rev = self._latest_revision(ud, d, name) 1695 rev = self._latest_revision(ud, d, name)
1671 return rev 1696 _revisions_cache.set_rev(key, rev)
1697 return rev
1672 1698
1673 def sortable_revision(self, ud, d, name): 1699 def sortable_revision(self, ud, d, name):
1674 latest_rev = self._build_revision(ud, d, name) 1700 latest_rev = self._build_revision(ud, d, name)
@@ -1806,7 +1832,7 @@ class Fetch(object):
1806 self.ud[url] = FetchData(url, self.d) 1832 self.ud[url] = FetchData(url, self.d)
1807 1833
1808 self.ud[url].setup_localpath(self.d) 1834 self.ud[url].setup_localpath(self.d)
1809 return self.d.expand(self.ud[url].localpath) 1835 return self.ud[url].localpath
1810 1836
1811 def localpaths(self): 1837 def localpaths(self):
1812 """ 1838 """
@@ -1859,25 +1885,28 @@ class Fetch(object):
1859 logger.debug(str(e)) 1885 logger.debug(str(e))
1860 done = False 1886 done = False
1861 1887
1888 d = self.d
1862 if premirroronly: 1889 if premirroronly:
1863 self.d.setVar("BB_NO_NETWORK", "1") 1890 # Only disable the network in a copy
1891 d = bb.data.createCopy(self.d)
1892 d.setVar("BB_NO_NETWORK", "1")
1864 1893
1865 firsterr = None 1894 firsterr = None
1866 verified_stamp = False 1895 verified_stamp = False
1867 if done: 1896 if done:
1868 verified_stamp = m.verify_donestamp(ud, self.d) 1897 verified_stamp = m.verify_donestamp(ud, d)
1869 if not done and (not verified_stamp or m.need_update(ud, self.d)): 1898 if not done and (not verified_stamp or m.need_update(ud, d)):
1870 try: 1899 try:
1871 if not trusted_network(self.d, ud.url): 1900 if not trusted_network(d, ud.url):
1872 raise UntrustedUrl(ud.url) 1901 raise UntrustedUrl(ud.url)
1873 logger.debug("Trying Upstream") 1902 logger.debug("Trying Upstream")
1874 m.download(ud, self.d) 1903 m.download(ud, d)
1875 if hasattr(m, "build_mirror_data"): 1904 if hasattr(m, "build_mirror_data"):
1876 m.build_mirror_data(ud, self.d) 1905 m.build_mirror_data(ud, d)
1877 done = True 1906 done = True
1878 # early checksum verify, so that if checksum mismatched, 1907 # early checksum verify, so that if checksum mismatched,
1879 # fetcher still have chance to fetch from mirror 1908 # fetcher still have chance to fetch from mirror
1880 m.update_donestamp(ud, self.d) 1909 m.update_donestamp(ud, d)
1881 1910
1882 except bb.fetch2.NetworkAccess: 1911 except bb.fetch2.NetworkAccess:
1883 raise 1912 raise
@@ -1896,17 +1925,17 @@ class Fetch(object):
1896 firsterr = e 1925 firsterr = e
1897 # Remove any incomplete fetch 1926 # Remove any incomplete fetch
1898 if not verified_stamp and m.cleanup_upon_failure(): 1927 if not verified_stamp and m.cleanup_upon_failure():
1899 m.clean(ud, self.d) 1928 m.clean(ud, d)
1900 logger.debug("Trying MIRRORS") 1929 logger.debug("Trying MIRRORS")
1901 mirrors = mirror_from_string(self.d.getVar('MIRRORS')) 1930 mirrors = mirror_from_string(d.getVar('MIRRORS'))
1902 done = m.try_mirrors(self, ud, self.d, mirrors) 1931 done = m.try_mirrors(self, ud, d, mirrors)
1903 1932
1904 if not done or not m.done(ud, self.d): 1933 if not done or not m.done(ud, d):
1905 if firsterr: 1934 if firsterr:
1906 logger.error(str(firsterr)) 1935 logger.error(str(firsterr))
1907 raise FetchError("Unable to fetch URL from any source.", u) 1936 raise FetchError("Unable to fetch URL from any source.", u)
1908 1937
1909 m.update_donestamp(ud, self.d) 1938 m.update_donestamp(ud, d)
1910 1939
1911 except IOError as e: 1940 except IOError as e:
1912 if e.errno in [errno.ESTALE]: 1941 if e.errno in [errno.ESTALE]:
@@ -2088,6 +2117,7 @@ from . import npmsw
2088from . import az 2117from . import az
2089from . import crate 2118from . import crate
2090from . import gcp 2119from . import gcp
2120from . import gomod
2091 2121
2092methods.append(local.Local()) 2122methods.append(local.Local())
2093methods.append(wget.Wget()) 2123methods.append(wget.Wget())
@@ -2110,3 +2140,5 @@ methods.append(npmsw.NpmShrinkWrap())
2110methods.append(az.Az()) 2140methods.append(az.Az())
2111methods.append(crate.Crate()) 2141methods.append(crate.Crate())
2112methods.append(gcp.GCP()) 2142methods.append(gcp.GCP())
2143methods.append(gomod.GoMod())
2144methods.append(gomod.GoModGit())
diff --git a/bitbake/lib/bb/fetch2/az.py b/bitbake/lib/bb/fetch2/az.py
index 3ccc594c22..1d3664f213 100644
--- a/bitbake/lib/bb/fetch2/az.py
+++ b/bitbake/lib/bb/fetch2/az.py
@@ -36,6 +36,8 @@ class Az(Wget):
36 36
37 az_sas = d.getVar('AZ_SAS') 37 az_sas = d.getVar('AZ_SAS')
38 if az_sas and az_sas not in ud.url: 38 if az_sas and az_sas not in ud.url:
39 if not az_sas.startswith('?'):
40 raise FetchError("When using AZ_SAS, it must start with a '?' character to mark the start of the query-parameters.")
39 ud.url += az_sas 41 ud.url += az_sas
40 42
41 return Wget.checkstatus(self, fetch, ud, d, try_again) 43 return Wget.checkstatus(self, fetch, ud, d, try_again)
@@ -62,15 +64,18 @@ class Az(Wget):
62 az_sas = d.getVar('AZ_SAS') 64 az_sas = d.getVar('AZ_SAS')
63 65
64 if az_sas: 66 if az_sas:
67 if not az_sas.startswith('?'):
68 raise FetchError("When using AZ_SAS, it must start with a '?' character to mark the start of the query-parameters.")
65 azuri = '%s%s%s%s' % ('https://', ud.host, ud.path, az_sas) 69 azuri = '%s%s%s%s' % ('https://', ud.host, ud.path, az_sas)
66 else: 70 else:
67 azuri = '%s%s%s' % ('https://', ud.host, ud.path) 71 azuri = '%s%s%s' % ('https://', ud.host, ud.path)
68 72
73 dldir = d.getVar("DL_DIR")
69 if os.path.exists(ud.localpath): 74 if os.path.exists(ud.localpath):
70 # file exists, but we didnt complete it.. trying again. 75 # file exists, but we didnt complete it.. trying again.
71 fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % azuri) 76 fetchcmd += " -c -P %s '%s'" % (dldir, azuri)
72 else: 77 else:
73 fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % azuri) 78 fetchcmd += " -P %s '%s'" % (dldir, azuri)
74 79
75 try: 80 try:
76 self._runwget(ud, d, fetchcmd, False) 81 self._runwget(ud, d, fetchcmd, False)
diff --git a/bitbake/lib/bb/fetch2/clearcase.py b/bitbake/lib/bb/fetch2/clearcase.py
index 1a9c863769..17500daf95 100644
--- a/bitbake/lib/bb/fetch2/clearcase.py
+++ b/bitbake/lib/bb/fetch2/clearcase.py
@@ -108,7 +108,7 @@ class ClearCase(FetchMethod):
108 ud.module.replace("/", "."), 108 ud.module.replace("/", "."),
109 ud.label.replace("/", ".")) 109 ud.label.replace("/", "."))
110 110
111 ud.viewname = "%s-view%s" % (ud.identifier, d.getVar("DATETIME", d, True)) 111 ud.viewname = "%s-view%s" % (ud.identifier, d.getVar("DATETIME"))
112 ud.csname = "%s-config-spec" % (ud.identifier) 112 ud.csname = "%s-config-spec" % (ud.identifier)
113 ud.ccasedir = os.path.join(d.getVar("DL_DIR"), ud.type) 113 ud.ccasedir = os.path.join(d.getVar("DL_DIR"), ud.type)
114 ud.viewdir = os.path.join(ud.ccasedir, ud.viewname) 114 ud.viewdir = os.path.join(ud.ccasedir, ud.viewname)
@@ -130,8 +130,6 @@ class ClearCase(FetchMethod):
130 self.debug("configspecfile = %s" % ud.configspecfile) 130 self.debug("configspecfile = %s" % ud.configspecfile)
131 self.debug("localfile = %s" % ud.localfile) 131 self.debug("localfile = %s" % ud.localfile)
132 132
133 ud.localfile = os.path.join(d.getVar("DL_DIR"), ud.localfile)
134
135 def _build_ccase_command(self, ud, command): 133 def _build_ccase_command(self, ud, command):
136 """ 134 """
137 Build up a commandline based on ud 135 Build up a commandline based on ud
@@ -196,7 +194,7 @@ class ClearCase(FetchMethod):
196 194
197 def need_update(self, ud, d): 195 def need_update(self, ud, d):
198 if ("LATEST" in ud.label) or (ud.customspec and "LATEST" in ud.customspec): 196 if ("LATEST" in ud.label) or (ud.customspec and "LATEST" in ud.customspec):
199 ud.identifier += "-%s" % d.getVar("DATETIME",d, True) 197 ud.identifier += "-%s" % d.getVar("DATETIME")
200 return True 198 return True
201 if os.path.exists(ud.localpath): 199 if os.path.exists(ud.localpath):
202 return False 200 return False
diff --git a/bitbake/lib/bb/fetch2/crate.py b/bitbake/lib/bb/fetch2/crate.py
index 01d49435c3..e611736f06 100644
--- a/bitbake/lib/bb/fetch2/crate.py
+++ b/bitbake/lib/bb/fetch2/crate.py
@@ -70,6 +70,7 @@ class Crate(Wget):
70 host = 'crates.io/api/v1/crates' 70 host = 'crates.io/api/v1/crates'
71 71
72 ud.url = "https://%s/%s/%s/download" % (host, name, version) 72 ud.url = "https://%s/%s/%s/download" % (host, name, version)
73 ud.versionsurl = "https://%s/%s/versions" % (host, name)
73 ud.parm['downloadfilename'] = "%s-%s.crate" % (name, version) 74 ud.parm['downloadfilename'] = "%s-%s.crate" % (name, version)
74 if 'name' not in ud.parm: 75 if 'name' not in ud.parm:
75 ud.parm['name'] = '%s-%s' % (name, version) 76 ud.parm['name'] = '%s-%s' % (name, version)
@@ -139,3 +140,11 @@ class Crate(Wget):
139 mdpath = os.path.join(bbpath, cratepath, mdfile) 140 mdpath = os.path.join(bbpath, cratepath, mdfile)
140 with open(mdpath, "w") as f: 141 with open(mdpath, "w") as f:
141 json.dump(metadata, f) 142 json.dump(metadata, f)
143
144 def latest_versionstring(self, ud, d):
145 from functools import cmp_to_key
146 json_data = json.loads(self._fetch_index(ud.versionsurl, ud, d))
147 versions = [(0, i["num"], "") for i in json_data["versions"]]
148 versions = sorted(versions, key=cmp_to_key(bb.utils.vercmp))
149
150 return (versions[-1][1], "")
diff --git a/bitbake/lib/bb/fetch2/gcp.py b/bitbake/lib/bb/fetch2/gcp.py
index f40ce2eaa5..86546d40bf 100644
--- a/bitbake/lib/bb/fetch2/gcp.py
+++ b/bitbake/lib/bb/fetch2/gcp.py
@@ -46,8 +46,7 @@ class GCP(FetchMethod):
46 else: 46 else:
47 ud.basename = os.path.basename(ud.path) 47 ud.basename = os.path.basename(ud.path)
48 48
49 ud.localfile = d.expand(urllib.parse.unquote(ud.basename)) 49 ud.localfile = ud.basename
50 ud.basecmd = "gsutil stat"
51 50
52 def get_gcp_client(self): 51 def get_gcp_client(self):
53 from google.cloud import storage 52 from google.cloud import storage
@@ -58,17 +57,20 @@ class GCP(FetchMethod):
58 Fetch urls using the GCP API. 57 Fetch urls using the GCP API.
59 Assumes localpath was called first. 58 Assumes localpath was called first.
60 """ 59 """
60 from google.api_core.exceptions import NotFound
61 logger.debug2(f"Trying to download gs://{ud.host}{ud.path} to {ud.localpath}") 61 logger.debug2(f"Trying to download gs://{ud.host}{ud.path} to {ud.localpath}")
62 if self.gcp_client is None: 62 if self.gcp_client is None:
63 self.get_gcp_client() 63 self.get_gcp_client()
64 64
65 bb.fetch2.check_network_access(d, ud.basecmd, f"gs://{ud.host}{ud.path}") 65 bb.fetch2.check_network_access(d, "blob.download_to_filename", f"gs://{ud.host}{ud.path}")
66 runfetchcmd("%s %s" % (ud.basecmd, f"gs://{ud.host}{ud.path}"), d)
67 66
68 # Path sometimes has leading slash, so strip it 67 # Path sometimes has leading slash, so strip it
69 path = ud.path.lstrip("/") 68 path = ud.path.lstrip("/")
70 blob = self.gcp_client.bucket(ud.host).blob(path) 69 blob = self.gcp_client.bucket(ud.host).blob(path)
71 blob.download_to_filename(ud.localpath) 70 try:
71 blob.download_to_filename(ud.localpath)
72 except NotFound:
73 raise FetchError("The GCP API threw a NotFound exception")
72 74
73 # Additional sanity checks copied from the wget class (although there 75 # Additional sanity checks copied from the wget class (although there
74 # are no known issues which mean these are required, treat the GCP API 76 # are no known issues which mean these are required, treat the GCP API
@@ -90,8 +92,7 @@ class GCP(FetchMethod):
90 if self.gcp_client is None: 92 if self.gcp_client is None:
91 self.get_gcp_client() 93 self.get_gcp_client()
92 94
93 bb.fetch2.check_network_access(d, ud.basecmd, f"gs://{ud.host}{ud.path}") 95 bb.fetch2.check_network_access(d, "gcp_client.bucket(ud.host).blob(path).exists()", f"gs://{ud.host}{ud.path}")
94 runfetchcmd("%s %s" % (ud.basecmd, f"gs://{ud.host}{ud.path}"), d)
95 96
96 # Path sometimes has leading slash, so strip it 97 # Path sometimes has leading slash, so strip it
97 path = ud.path.lstrip("/") 98 path = ud.path.lstrip("/")
diff --git a/bitbake/lib/bb/fetch2/git.py b/bitbake/lib/bb/fetch2/git.py
index c7ff769fdf..14ec45a3f6 100644
--- a/bitbake/lib/bb/fetch2/git.py
+++ b/bitbake/lib/bb/fetch2/git.py
@@ -9,15 +9,6 @@ Supported SRC_URI options are:
9- branch 9- branch
10 The git branch to retrieve from. The default is "master" 10 The git branch to retrieve from. The default is "master"
11 11
12 This option also supports multiple branch fetching, with branches
13 separated by commas. In multiple branches case, the name option
14 must have the same number of names to match the branches, which is
15 used to specify the SRC_REV for the branch
16 e.g:
17 SRC_URI="git://some.host/somepath;branch=branchX,branchY;name=nameX,nameY"
18 SRCREV_nameX = "xxxxxxxxxxxxxxxxxxxx"
19 SRCREV_nameY = "YYYYYYYYYYYYYYYYYYYY"
20
21- tag 12- tag
22 The git tag to retrieve. The default is "master" 13 The git tag to retrieve. The default is "master"
23 14
@@ -81,6 +72,7 @@ import shlex
81import shutil 72import shutil
82import subprocess 73import subprocess
83import tempfile 74import tempfile
75import urllib
84import bb 76import bb
85import bb.progress 77import bb.progress
86from contextlib import contextmanager 78from contextlib import contextmanager
@@ -190,14 +182,11 @@ class Git(FetchMethod):
190 ud.bareclone = ud.parm.get("bareclone","0") == "1" 182 ud.bareclone = ud.parm.get("bareclone","0") == "1"
191 if ud.bareclone: 183 if ud.bareclone:
192 ud.nocheckout = 1 184 ud.nocheckout = 1
193 185
194 ud.unresolvedrev = {} 186 ud.unresolvedrev = ""
195 branches = ud.parm.get("branch", "").split(',') 187 ud.branch = ud.parm.get("branch", "")
196 if branches == [""] and not ud.nobranch: 188 if not ud.branch and not ud.nobranch:
197 bb.warn("URL: %s does not set any branch parameter. The future default branch used by tools and repositories is uncertain and we will therefore soon require this is set in all git urls." % ud.url) 189 raise bb.fetch2.ParameterError("The url does not set any branch parameter or set nobranch=1.", ud.url)
198 branches = ["master"]
199 if len(branches) != len(ud.names):
200 raise bb.fetch2.ParameterError("The number of name and branch parameters is not balanced", ud.url)
201 190
202 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1" 191 ud.noshared = d.getVar("BB_GIT_NOSHARED") == "1"
203 192
@@ -207,8 +196,11 @@ class Git(FetchMethod):
207 if ud.bareclone: 196 if ud.bareclone:
208 ud.cloneflags += " --mirror" 197 ud.cloneflags += " --mirror"
209 198
199 ud.shallow_skip_fast = False
210 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1" 200 ud.shallow = d.getVar("BB_GIT_SHALLOW") == "1"
211 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split() 201 ud.shallow_extra_refs = (d.getVar("BB_GIT_SHALLOW_EXTRA_REFS") or "").split()
202 if 'tag' in ud.parm:
203 ud.shallow_extra_refs.append("refs/tags/" + ud.parm['tag'])
212 204
213 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH") 205 depth_default = d.getVar("BB_GIT_SHALLOW_DEPTH")
214 if depth_default is not None: 206 if depth_default is not None:
@@ -225,32 +217,27 @@ class Git(FetchMethod):
225 217
226 revs_default = d.getVar("BB_GIT_SHALLOW_REVS") 218 revs_default = d.getVar("BB_GIT_SHALLOW_REVS")
227 ud.shallow_revs = [] 219 ud.shallow_revs = []
228 ud.branches = {} 220
229 for pos, name in enumerate(ud.names): 221 ud.unresolvedrev = ud.branch
230 branch = branches[pos] 222
231 ud.branches[name] = branch 223 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % ud.name)
232 ud.unresolvedrev[name] = branch 224 if shallow_depth is not None:
233 225 try:
234 shallow_depth = d.getVar("BB_GIT_SHALLOW_DEPTH_%s" % name) 226 shallow_depth = int(shallow_depth or 0)
235 if shallow_depth is not None: 227 except ValueError:
236 try: 228 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (ud.name, shallow_depth))
237 shallow_depth = int(shallow_depth or 0) 229 else:
238 except ValueError: 230 if shallow_depth < 0:
239 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth)) 231 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (ud.name, shallow_depth))
240 else: 232 ud.shallow_depths[ud.name] = shallow_depth
241 if shallow_depth < 0: 233
242 raise bb.fetch2.FetchError("Invalid depth for BB_GIT_SHALLOW_DEPTH_%s: %s" % (name, shallow_depth)) 234 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % ud.name)
243 ud.shallow_depths[name] = shallow_depth 235 if revs is not None:
244 236 ud.shallow_revs.extend(revs.split())
245 revs = d.getVar("BB_GIT_SHALLOW_REVS_%s" % name) 237 elif revs_default is not None:
246 if revs is not None: 238 ud.shallow_revs.extend(revs_default.split())
247 ud.shallow_revs.extend(revs.split()) 239
248 elif revs_default is not None: 240 if ud.shallow and not ud.shallow_revs and ud.shallow_depths[ud.name] == 0:
249 ud.shallow_revs.extend(revs_default.split())
250
251 if (ud.shallow and
252 not ud.shallow_revs and
253 all(ud.shallow_depths[n] == 0 for n in ud.names)):
254 # Shallow disabled for this URL 241 # Shallow disabled for this URL
255 ud.shallow = False 242 ud.shallow = False
256 243
@@ -259,10 +246,9 @@ class Git(FetchMethod):
259 # rev of this repository. This will get resolved into a revision 246 # rev of this repository. This will get resolved into a revision
260 # later. If an actual revision happens to have also been provided 247 # later. If an actual revision happens to have also been provided
261 # then this setting will be overridden. 248 # then this setting will be overridden.
262 for name in ud.names: 249 ud.unresolvedrev = 'HEAD'
263 ud.unresolvedrev[name] = 'HEAD'
264 250
265 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c gc.autoDetach=false -c core.pager=cat -c safe.bareRepository=all" 251 ud.basecmd = d.getVar("FETCHCMD_git") or "git -c gc.autoDetach=false -c core.pager=cat -c safe.bareRepository=all -c clone.defaultRemoteName=origin"
266 252
267 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0" 253 write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS") or "0"
268 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable 254 ud.write_tarballs = write_tarballs != "0" or ud.rebaseable
@@ -270,12 +256,11 @@ class Git(FetchMethod):
270 256
271 ud.setup_revisions(d) 257 ud.setup_revisions(d)
272 258
273 for name in ud.names: 259 # Ensure any revision that doesn't look like a SHA-1 is translated into one
274 # Ensure any revision that doesn't look like a SHA-1 is translated into one 260 if not sha1_re.match(ud.revision or ''):
275 if not sha1_re.match(ud.revisions[name] or ''): 261 if ud.revision:
276 if ud.revisions[name]: 262 ud.unresolvedrev = ud.revision
277 ud.unresolvedrev[name] = ud.revisions[name] 263 ud.revision = self.latest_revision(ud, d, ud.name)
278 ud.revisions[name] = self.latest_revision(ud, d, name)
279 264
280 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_').replace('(', '_').replace(')', '_')) 265 gitsrcname = '%s%s' % (ud.host.replace(':', '.'), ud.path.replace('/', '.').replace('*', '.').replace(' ','_').replace('(', '_').replace(')', '_'))
281 if gitsrcname.startswith('.'): 266 if gitsrcname.startswith('.'):
@@ -286,8 +271,7 @@ class Git(FetchMethod):
286 # upstream repo in the future, the mirror will remain intact and still 271 # upstream repo in the future, the mirror will remain intact and still
287 # contain the revision 272 # contain the revision
288 if ud.rebaseable: 273 if ud.rebaseable:
289 for name in ud.names: 274 gitsrcname = gitsrcname + '_' + ud.revision
290 gitsrcname = gitsrcname + '_' + ud.revisions[name]
291 275
292 dl_dir = d.getVar("DL_DIR") 276 dl_dir = d.getVar("DL_DIR")
293 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2") 277 gitdir = d.getVar("GITDIR") or (dl_dir + "/git2")
@@ -305,15 +289,14 @@ class Git(FetchMethod):
305 if ud.shallow_revs: 289 if ud.shallow_revs:
306 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs))) 290 tarballname = "%s_%s" % (tarballname, "_".join(sorted(ud.shallow_revs)))
307 291
308 for name, revision in sorted(ud.revisions.items()): 292 tarballname = "%s_%s" % (tarballname, ud.revision[:7])
309 tarballname = "%s_%s" % (tarballname, ud.revisions[name][:7]) 293 depth = ud.shallow_depths[ud.name]
310 depth = ud.shallow_depths[name] 294 if depth:
311 if depth: 295 tarballname = "%s-%s" % (tarballname, depth)
312 tarballname = "%s-%s" % (tarballname, depth)
313 296
314 shallow_refs = [] 297 shallow_refs = []
315 if not ud.nobranch: 298 if not ud.nobranch:
316 shallow_refs.extend(ud.branches.values()) 299 shallow_refs.append(ud.branch)
317 if ud.shallow_extra_refs: 300 if ud.shallow_extra_refs:
318 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs) 301 shallow_refs.extend(r.replace('refs/heads/', '').replace('*', 'ALL') for r in ud.shallow_extra_refs)
319 if shallow_refs: 302 if shallow_refs:
@@ -338,18 +321,19 @@ class Git(FetchMethod):
338 return True 321 return True
339 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d): 322 if ud.shallow and ud.write_shallow_tarballs and self.clonedir_need_shallow_revs(ud, d):
340 return True 323 return True
341 for name in ud.names: 324 if not self._contains_ref(ud, d, ud.name, ud.clonedir):
342 if not self._contains_ref(ud, d, name, ud.clonedir): 325 return True
343 return True
344 return False 326 return False
345 327
346 def lfs_need_update(self, ud, d): 328 def lfs_need_update(self, ud, d):
329 if not self._need_lfs(ud):
330 return False
331
347 if self.clonedir_need_update(ud, d): 332 if self.clonedir_need_update(ud, d):
348 return True 333 return True
349 334
350 for name in ud.names: 335 if not self._lfs_objects_downloaded(ud, d, ud.clonedir):
351 if not self._lfs_objects_downloaded(ud, d, name, ud.clonedir): 336 return True
352 return True
353 return False 337 return False
354 338
355 def clonedir_need_shallow_revs(self, ud, d): 339 def clonedir_need_shallow_revs(self, ud, d):
@@ -366,6 +350,13 @@ class Git(FetchMethod):
366 def tarball_need_update(self, ud): 350 def tarball_need_update(self, ud):
367 return ud.write_tarballs and not os.path.exists(ud.fullmirror) 351 return ud.write_tarballs and not os.path.exists(ud.fullmirror)
368 352
353 def update_mirror_links(self, ud, origud):
354 super().update_mirror_links(ud, origud)
355 # When using shallow mode, add a symlink to the original fullshallow
356 # path to ensure a valid symlink even in the `PREMIRRORS` case
357 if ud.shallow and not os.path.exists(origud.fullshallow):
358 self.ensure_symlink(ud.localpath, origud.fullshallow)
359
369 def try_premirror(self, ud, d): 360 def try_premirror(self, ud, d):
370 # If we don't do this, updating an existing checkout with only premirrors 361 # If we don't do this, updating an existing checkout with only premirrors
371 # is not possible 362 # is not possible
@@ -446,6 +437,24 @@ class Git(FetchMethod):
446 if ud.proto.lower() != 'file': 437 if ud.proto.lower() != 'file':
447 bb.fetch2.check_network_access(d, clone_cmd, ud.url) 438 bb.fetch2.check_network_access(d, clone_cmd, ud.url)
448 progresshandler = GitProgressHandler(d) 439 progresshandler = GitProgressHandler(d)
440
441 # Try creating a fast initial shallow clone
442 # Enabling ud.shallow_skip_fast will skip this
443 # If the Git error "Server does not allow request for unadvertised object"
444 # occurs, shallow_skip_fast is enabled automatically.
445 # This may happen if the Git server does not allow the request
446 # or if the Git client has issues with this functionality.
447 if ud.shallow and not ud.shallow_skip_fast:
448 try:
449 self.clone_shallow_with_tarball(ud, d)
450 # When the shallow clone has succeeded, use the shallow tarball
451 ud.localpath = ud.fullshallow
452 return
453 except:
454 logger.warning("Creating fast initial shallow clone failed, try initial regular clone now.")
455
456 # When skipping fast initial shallow or the fast inital shallow clone failed:
457 # Try again with an initial regular clone
449 runfetchcmd(clone_cmd, d, log=progresshandler) 458 runfetchcmd(clone_cmd, d, log=progresshandler)
450 459
451 # Update the checkout if needed 460 # Update the checkout if needed
@@ -473,9 +482,8 @@ class Git(FetchMethod):
473 if exc.errno != errno.ENOENT: 482 if exc.errno != errno.ENOENT:
474 raise 483 raise
475 484
476 for name in ud.names: 485 if not self._contains_ref(ud, d, ud.name, ud.clonedir):
477 if not self._contains_ref(ud, d, name, ud.clonedir): 486 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revision, ud.branch))
478 raise bb.fetch2.FetchError("Unable to find revision %s in branch %s even from upstream" % (ud.revisions[name], ud.branches[name]))
479 487
480 if ud.shallow and ud.write_shallow_tarballs: 488 if ud.shallow and ud.write_shallow_tarballs:
481 missing_rev = self.clonedir_need_shallow_revs(ud, d) 489 missing_rev = self.clonedir_need_shallow_revs(ud, d)
@@ -483,128 +491,168 @@ class Git(FetchMethod):
483 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev) 491 raise bb.fetch2.FetchError("Unable to find revision %s even from upstream" % missing_rev)
484 492
485 if self.lfs_need_update(ud, d): 493 if self.lfs_need_update(ud, d):
486 # Unpack temporary working copy, use it to run 'git checkout' to force pre-fetching 494 self.lfs_fetch(ud, d, ud.clonedir, ud.revision)
487 # of all LFS blobs needed at the srcrev.
488 #
489 # It would be nice to just do this inline here by running 'git-lfs fetch'
490 # on the bare clonedir, but that operation requires a working copy on some
491 # releases of Git LFS.
492 with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
493 # Do the checkout. This implicitly involves a Git LFS fetch.
494 Git.unpack(self, ud, tmpdir, d)
495
496 # Scoop up a copy of any stuff that Git LFS downloaded. Merge them into
497 # the bare clonedir.
498 #
499 # As this procedure is invoked repeatedly on incremental fetches as
500 # a recipe's SRCREV is bumped throughout its lifetime, this will
501 # result in a gradual accumulation of LFS blobs in <ud.clonedir>/lfs
502 # corresponding to all the blobs reachable from the different revs
503 # fetched across time.
504 #
505 # Only do this if the unpack resulted in a .git/lfs directory being
506 # created; this only happens if at least one blob needed to be
507 # downloaded.
508 if os.path.exists(os.path.join(ud.destdir, ".git", "lfs")):
509 runfetchcmd("tar -cf - lfs | tar -xf - -C %s" % ud.clonedir, d, workdir="%s/.git" % ud.destdir)
510
511 def build_mirror_data(self, ud, d):
512 495
513 # Create as a temp file and move atomically into position to avoid races 496 def lfs_fetch(self, ud, d, clonedir, revision, fetchall=False, progresshandler=None):
514 @contextmanager 497 """Helper method for fetching Git LFS data"""
515 def create_atomic(filename): 498 try:
516 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename)) 499 if self._need_lfs(ud) and self._contains_lfs(ud, d, clonedir) and len(revision):
517 try: 500 self._ensure_git_lfs(d, ud)
518 yield tfile 501
519 umask = os.umask(0o666) 502 # Using worktree with the revision because .lfsconfig may exists
520 os.umask(umask) 503 worktree_add_cmd = "%s worktree add wt %s" % (ud.basecmd, revision)
521 os.chmod(tfile, (0o666 & ~umask)) 504 runfetchcmd(worktree_add_cmd, d, log=progresshandler, workdir=clonedir)
522 os.rename(tfile, filename) 505 lfs_fetch_cmd = "%s lfs fetch %s" % (ud.basecmd, "--all" if fetchall else "")
523 finally: 506 runfetchcmd(lfs_fetch_cmd, d, log=progresshandler, workdir=(clonedir + "/wt"))
524 os.close(fd) 507 worktree_rem_cmd = "%s worktree remove -f wt" % ud.basecmd
508 runfetchcmd(worktree_rem_cmd, d, log=progresshandler, workdir=clonedir)
509 except:
510 logger.warning("Fetching LFS did not succeed.")
511
512 @contextmanager
513 def create_atomic(self, filename):
514 """Create as a temp file and move atomically into position to avoid races"""
515 fd, tfile = tempfile.mkstemp(dir=os.path.dirname(filename))
516 try:
517 yield tfile
518 umask = os.umask(0o666)
519 os.umask(umask)
520 os.chmod(tfile, (0o666 & ~umask))
521 os.rename(tfile, filename)
522 finally:
523 os.close(fd)
525 524
525 def build_mirror_data(self, ud, d):
526 if ud.shallow and ud.write_shallow_tarballs: 526 if ud.shallow and ud.write_shallow_tarballs:
527 if not os.path.exists(ud.fullshallow): 527 if not os.path.exists(ud.fullshallow):
528 if os.path.islink(ud.fullshallow): 528 if os.path.islink(ud.fullshallow):
529 os.unlink(ud.fullshallow) 529 os.unlink(ud.fullshallow)
530 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR')) 530 self.clone_shallow_with_tarball(ud, d)
531 shallowclone = os.path.join(tempdir, 'git')
532 try:
533 self.clone_shallow_local(ud, shallowclone, d)
534
535 logger.info("Creating tarball of git repository")
536 with create_atomic(ud.fullshallow) as tfile:
537 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
538 runfetchcmd("touch %s.done" % ud.fullshallow, d)
539 finally:
540 bb.utils.remove(tempdir, recurse=True)
541 elif ud.write_tarballs and not os.path.exists(ud.fullmirror): 531 elif ud.write_tarballs and not os.path.exists(ud.fullmirror):
542 if os.path.islink(ud.fullmirror): 532 if os.path.islink(ud.fullmirror):
543 os.unlink(ud.fullmirror) 533 os.unlink(ud.fullmirror)
544 534
545 logger.info("Creating tarball of git repository") 535 logger.info("Creating tarball of git repository")
546 with create_atomic(ud.fullmirror) as tfile: 536 with self.create_atomic(ud.fullmirror) as tfile:
547 mtime = runfetchcmd("{} log --all -1 --format=%cD".format(ud.basecmd), d, 537 mtime = runfetchcmd("{} log --all -1 --format=%cD".format(ud.basecmd), d,
548 quiet=True, workdir=ud.clonedir) 538 quiet=True, workdir=ud.clonedir)
549 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ." 539 runfetchcmd("tar -czf %s --owner oe:0 --group oe:0 --mtime \"%s\" ."
550 % (tfile, mtime), d, workdir=ud.clonedir) 540 % (tfile, mtime), d, workdir=ud.clonedir)
551 runfetchcmd("touch %s.done" % ud.fullmirror, d) 541 runfetchcmd("touch %s.done" % ud.fullmirror, d)
552 542
543 def clone_shallow_with_tarball(self, ud, d):
544 ret = False
545 tempdir = tempfile.mkdtemp(dir=d.getVar('DL_DIR'))
546 shallowclone = os.path.join(tempdir, 'git')
547 try:
548 try:
549 self.clone_shallow_local(ud, shallowclone, d)
550 except:
551 logger.warning("Fast shallow clone failed, try to skip fast mode now.")
552 bb.utils.remove(tempdir, recurse=True)
553 os.mkdir(tempdir)
554 ud.shallow_skip_fast = True
555 self.clone_shallow_local(ud, shallowclone, d)
556 logger.info("Creating tarball of git repository")
557 with self.create_atomic(ud.fullshallow) as tfile:
558 runfetchcmd("tar -czf %s ." % tfile, d, workdir=shallowclone)
559 runfetchcmd("touch %s.done" % ud.fullshallow, d)
560 ret = True
561 finally:
562 bb.utils.remove(tempdir, recurse=True)
563
564 return ret
565
553 def clone_shallow_local(self, ud, dest, d): 566 def clone_shallow_local(self, ud, dest, d):
554 """Clone the repo and make it shallow. 567 """
568 Shallow fetch from ud.clonedir (${DL_DIR}/git2/<gitrepo> by default):
569 - For BB_GIT_SHALLOW_DEPTH: git fetch --depth <depth> rev
570 - For BB_GIT_SHALLOW_REVS: git fetch --shallow-exclude=<revs> rev
571 """
555 572
556 The upstream url of the new clone isn't set at this time, as it'll be 573 progresshandler = GitProgressHandler(d)
557 set correctly when unpacked.""" 574 repourl = self._get_repo_url(ud)
558 runfetchcmd("%s clone %s %s %s" % (ud.basecmd, ud.cloneflags, ud.clonedir, dest), d) 575 bb.utils.mkdirhier(dest)
576 init_cmd = "%s init -q" % ud.basecmd
577 if ud.bareclone:
578 init_cmd += " --bare"
579 runfetchcmd(init_cmd, d, workdir=dest)
580 # Use repourl when creating a fast initial shallow clone
581 # Prefer already existing full bare clones if available
582 if not ud.shallow_skip_fast and not os.path.exists(ud.clonedir):
583 remote = shlex.quote(repourl)
584 else:
585 remote = ud.clonedir
586 runfetchcmd("%s remote add origin %s" % (ud.basecmd, remote), d, workdir=dest)
559 587
560 to_parse, shallow_branches = [], [] 588 # Check the histories which should be excluded
561 for name in ud.names: 589 shallow_exclude = ''
562 revision = ud.revisions[name] 590 for revision in ud.shallow_revs:
563 depth = ud.shallow_depths[name] 591 shallow_exclude += " --shallow-exclude=%s" % revision
564 if depth:
565 to_parse.append('%s~%d^{}' % (revision, depth - 1))
566 592
567 # For nobranch, we need a ref, otherwise the commits will be 593 revision = ud.revision
568 # removed, and for non-nobranch, we truncate the branch to our 594 depth = ud.shallow_depths[ud.name]
569 # srcrev, to avoid keeping unnecessary history beyond that.
570 branch = ud.branches[name]
571 if ud.nobranch:
572 ref = "refs/shallow/%s" % name
573 elif ud.bareclone:
574 ref = "refs/heads/%s" % branch
575 else:
576 ref = "refs/remotes/origin/%s" % branch
577 595
578 shallow_branches.append(ref) 596 # The --depth and --shallow-exclude can't be used together
579 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest) 597 if depth and shallow_exclude:
598 raise bb.fetch2.FetchError("BB_GIT_SHALLOW_REVS is set, but BB_GIT_SHALLOW_DEPTH is not 0.")
599
600 # For nobranch, we need a ref, otherwise the commits will be
601 # removed, and for non-nobranch, we truncate the branch to our
602 # srcrev, to avoid keeping unnecessary history beyond that.
603 branch = ud.branch
604 if ud.nobranch:
605 ref = "refs/shallow/%s" % ud.name
606 elif ud.bareclone:
607 ref = "refs/heads/%s" % branch
608 else:
609 ref = "refs/remotes/origin/%s" % branch
610
611 fetch_cmd = "%s fetch origin %s" % (ud.basecmd, revision)
612 if depth:
613 fetch_cmd += " --depth %s" % depth
614
615 if shallow_exclude:
616 fetch_cmd += shallow_exclude
580 617
581 # Map srcrev+depths to revisions 618 # Advertise the revision for lower version git such as 2.25.1:
582 parsed_depths = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join(to_parse)), d, workdir=dest) 619 # error: Server does not allow request for unadvertised object.
620 # The ud.clonedir is a local temporary dir, will be removed when
621 # fetch is done, so we can do anything on it.
622 adv_cmd = 'git branch -f advertise-%s %s' % (revision, revision)
623 if ud.shallow_skip_fast:
624 runfetchcmd(adv_cmd, d, workdir=ud.clonedir)
583 625
584 # Resolve specified revisions 626 runfetchcmd(fetch_cmd, d, workdir=dest)
585 parsed_revs = runfetchcmd("%s rev-parse %s" % (ud.basecmd, " ".join('"%s^{}"' % r for r in ud.shallow_revs)), d, workdir=dest) 627 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
586 shallow_revisions = parsed_depths.splitlines() + parsed_revs.splitlines() 628 # Fetch Git LFS data
629 self.lfs_fetch(ud, d, dest, ud.revision)
587 630
588 # Apply extra ref wildcards 631 # Apply extra ref wildcards
589 all_refs = runfetchcmd('%s for-each-ref "--format=%%(refname)"' % ud.basecmd, 632 all_refs_remote = runfetchcmd("%s ls-remote origin 'refs/*'" % ud.basecmd, \
590 d, workdir=dest).splitlines() 633 d, workdir=dest).splitlines()
634 all_refs = []
635 for line in all_refs_remote:
636 all_refs.append(line.split()[-1])
637 extra_refs = []
591 for r in ud.shallow_extra_refs: 638 for r in ud.shallow_extra_refs:
592 if not ud.bareclone: 639 if not ud.bareclone:
593 r = r.replace('refs/heads/', 'refs/remotes/origin/') 640 r = r.replace('refs/heads/', 'refs/remotes/origin/')
594 641
595 if '*' in r: 642 if '*' in r:
596 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs) 643 matches = filter(lambda a: fnmatch.fnmatchcase(a, r), all_refs)
597 shallow_branches.extend(matches) 644 extra_refs.extend(matches)
598 else: 645 else:
599 shallow_branches.append(r) 646 extra_refs.append(r)
600 647
601 # Make the repository shallow 648 for ref in extra_refs:
602 shallow_cmd = [self.make_shallow_path, '-s'] 649 ref_fetch = ref.replace('refs/heads/', '').replace('refs/remotes/origin/', '').replace('refs/tags/', '')
603 for b in shallow_branches: 650 runfetchcmd("%s fetch origin --depth 1 %s" % (ud.basecmd, ref_fetch), d, workdir=dest)
604 shallow_cmd.append('-r') 651 revision = runfetchcmd("%s rev-parse FETCH_HEAD" % ud.basecmd, d, workdir=dest)
605 shallow_cmd.append(b) 652 runfetchcmd("%s update-ref %s %s" % (ud.basecmd, ref, revision), d, workdir=dest)
606 shallow_cmd.extend(shallow_revisions) 653
607 runfetchcmd(subprocess.list2cmdline(shallow_cmd), d, workdir=dest) 654 # The url is local ud.clonedir, set it to upstream one
655 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=dest)
608 656
609 def unpack(self, ud, destdir, d): 657 def unpack(self, ud, destdir, d):
610 """ unpack the downloaded src to destdir""" 658 """ unpack the downloaded src to destdir"""
@@ -612,7 +660,7 @@ class Git(FetchMethod):
612 subdir = ud.parm.get("subdir") 660 subdir = ud.parm.get("subdir")
613 subpath = ud.parm.get("subpath") 661 subpath = ud.parm.get("subpath")
614 readpathspec = "" 662 readpathspec = ""
615 def_destsuffix = "git/" 663 def_destsuffix = (d.getVar("BB_GIT_DEFAULT_DESTSUFFIX") or "git") + "/"
616 664
617 if subpath: 665 if subpath:
618 readpathspec = ":%s" % subpath 666 readpathspec = ":%s" % subpath
@@ -664,30 +712,43 @@ class Git(FetchMethod):
664 if not source_found: 712 if not source_found:
665 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url) 713 raise bb.fetch2.UnpackError("No up to date source found: " + "; ".join(source_error), ud.url)
666 714
715 # If there is a tag parameter in the url and we also have a fixed srcrev, check the tag
716 # matches the revision
717 if 'tag' in ud.parm and sha1_re.match(ud.revision):
718 output = runfetchcmd("%s rev-list -n 1 %s" % (ud.basecmd, ud.parm['tag']), d, workdir=destdir)
719 output = output.strip()
720 if output != ud.revision:
721 # It is possible ud.revision is the revision on an annotated tag which won't match the output of rev-list
722 # If it resolves to the same thing there isn't a problem.
723 output2 = runfetchcmd("%s rev-list -n 1 %s" % (ud.basecmd, ud.revision), d, workdir=destdir)
724 output2 = output2.strip()
725 if output != output2:
726 raise bb.fetch2.FetchError("The revision the git tag '%s' resolved to didn't match the SRCREV in use (%s vs %s)" % (ud.parm['tag'], output, ud.revision), ud.url)
727
667 repourl = self._get_repo_url(ud) 728 repourl = self._get_repo_url(ud)
668 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir) 729 runfetchcmd("%s remote set-url origin %s" % (ud.basecmd, shlex.quote(repourl)), d, workdir=destdir)
669 730
670 if self._contains_lfs(ud, d, destdir): 731 if self._contains_lfs(ud, d, destdir):
671 if need_lfs and not self._find_git_lfs(d): 732 if not need_lfs:
672 raise bb.fetch2.FetchError("Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 to ignore it)" % (repourl))
673 elif not need_lfs:
674 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl)) 733 bb.note("Repository %s has LFS content but it is not being fetched" % (repourl))
675 else: 734 else:
735 self._ensure_git_lfs(d, ud)
736
676 runfetchcmd("%s lfs install --local" % ud.basecmd, d, workdir=destdir) 737 runfetchcmd("%s lfs install --local" % ud.basecmd, d, workdir=destdir)
677 738
678 if not ud.nocheckout: 739 if not ud.nocheckout:
679 if subpath: 740 if subpath:
680 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revisions[ud.names[0]], readpathspec), d, 741 runfetchcmd("%s read-tree %s%s" % (ud.basecmd, ud.revision, readpathspec), d,
681 workdir=destdir) 742 workdir=destdir)
682 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir) 743 runfetchcmd("%s checkout-index -q -f -a" % ud.basecmd, d, workdir=destdir)
683 elif not ud.nobranch: 744 elif not ud.nobranch:
684 branchname = ud.branches[ud.names[0]] 745 branchname = ud.branch
685 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \ 746 runfetchcmd("%s checkout -B %s %s" % (ud.basecmd, branchname, \
686 ud.revisions[ud.names[0]]), d, workdir=destdir) 747 ud.revision), d, workdir=destdir)
687 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \ 748 runfetchcmd("%s branch %s --set-upstream-to origin/%s" % (ud.basecmd, branchname, \
688 branchname), d, workdir=destdir) 749 branchname), d, workdir=destdir)
689 else: 750 else:
690 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revisions[ud.names[0]]), d, workdir=destdir) 751 runfetchcmd("%s checkout %s" % (ud.basecmd, ud.revision), d, workdir=destdir)
691 752
692 return True 753 return True
693 754
@@ -701,8 +762,13 @@ class Git(FetchMethod):
701 clonedir = os.path.realpath(ud.localpath) 762 clonedir = os.path.realpath(ud.localpath)
702 to_remove.append(clonedir) 763 to_remove.append(clonedir)
703 764
765 # Remove shallow mirror tarball
766 if ud.shallow:
767 to_remove.append(ud.fullshallow)
768 to_remove.append(ud.fullshallow + ".done")
769
704 for r in to_remove: 770 for r in to_remove:
705 if os.path.exists(r): 771 if os.path.exists(r) or os.path.islink(r):
706 bb.note('Removing %s' % r) 772 bb.note('Removing %s' % r)
707 bb.utils.remove(r, True) 773 bb.utils.remove(r, True)
708 774
@@ -713,10 +779,10 @@ class Git(FetchMethod):
713 cmd = "" 779 cmd = ""
714 if ud.nobranch: 780 if ud.nobranch:
715 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % ( 781 cmd = "%s log --pretty=oneline -n 1 %s -- 2> /dev/null | wc -l" % (
716 ud.basecmd, ud.revisions[name]) 782 ud.basecmd, ud.revision)
717 else: 783 else:
718 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % ( 784 cmd = "%s branch --contains %s --list %s 2> /dev/null | wc -l" % (
719 ud.basecmd, ud.revisions[name], ud.branches[name]) 785 ud.basecmd, ud.revision, ud.branch)
720 try: 786 try:
721 output = runfetchcmd(cmd, d, quiet=True, workdir=wd) 787 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
722 except bb.fetch2.FetchError: 788 except bb.fetch2.FetchError:
@@ -725,19 +791,21 @@ class Git(FetchMethod):
725 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output)) 791 raise bb.fetch2.FetchError("The command '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
726 return output.split()[0] != "0" 792 return output.split()[0] != "0"
727 793
728 def _lfs_objects_downloaded(self, ud, d, name, wd): 794 def _lfs_objects_downloaded(self, ud, d, wd):
729 """ 795 """
730 Verifies whether the LFS objects for requested revisions have already been downloaded 796 Verifies whether the LFS objects for requested revisions have already been downloaded
731 """ 797 """
732 # Bail out early if this repository doesn't use LFS 798 # Bail out early if this repository doesn't use LFS
733 if not self._need_lfs(ud) or not self._contains_lfs(ud, d, wd): 799 if not self._contains_lfs(ud, d, wd):
734 return True 800 return True
735 801
802 self._ensure_git_lfs(d, ud)
803
736 # The Git LFS specification specifies ([1]) the LFS folder layout so it should be safe to check for file 804 # The Git LFS specification specifies ([1]) the LFS folder layout so it should be safe to check for file
737 # existence. 805 # existence.
738 # [1] https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git 806 # [1] https://github.com/git-lfs/git-lfs/blob/main/docs/spec.md#intercepting-git
739 cmd = "%s lfs ls-files -l %s" \ 807 cmd = "%s lfs ls-files -l %s" \
740 % (ud.basecmd, ud.revisions[name]) 808 % (ud.basecmd, ud.revision)
741 output = runfetchcmd(cmd, d, quiet=True, workdir=wd).rstrip() 809 output = runfetchcmd(cmd, d, quiet=True, workdir=wd).rstrip()
742 # Do not do any further matching if no objects are managed by LFS 810 # Do not do any further matching if no objects are managed by LFS
743 if not output: 811 if not output:
@@ -761,18 +829,8 @@ class Git(FetchMethod):
761 """ 829 """
762 Check if the repository has 'lfs' (large file) content 830 Check if the repository has 'lfs' (large file) content
763 """ 831 """
764
765 if ud.nobranch:
766 # If no branch is specified, use the current git commit
767 refname = self._build_revision(ud, d, ud.names[0])
768 elif wd == ud.clonedir:
769 # The bare clonedir doesn't use the remote names; it has the branch immediately.
770 refname = ud.branches[ud.names[0]]
771 else:
772 refname = "origin/%s" % ud.branches[ud.names[0]]
773
774 cmd = "%s grep lfs %s:.gitattributes | wc -l" % ( 832 cmd = "%s grep lfs %s:.gitattributes | wc -l" % (
775 ud.basecmd, refname) 833 ud.basecmd, ud.revision)
776 834
777 try: 835 try:
778 output = runfetchcmd(cmd, d, quiet=True, workdir=wd) 836 output = runfetchcmd(cmd, d, quiet=True, workdir=wd)
@@ -782,12 +840,14 @@ class Git(FetchMethod):
782 pass 840 pass
783 return False 841 return False
784 842
785 def _find_git_lfs(self, d): 843 def _ensure_git_lfs(self, d, ud):
786 """ 844 """
787 Return True if git-lfs can be found, False otherwise. 845 Ensures that git-lfs is available, raising a FetchError if it isn't.
788 """ 846 """
789 import shutil 847 if shutil.which("git-lfs", path=d.getVar('PATH')) is None:
790 return shutil.which("git-lfs", path=d.getVar('PATH')) is not None 848 raise bb.fetch2.FetchError(
849 "Repository %s has LFS content, install git-lfs on host to download (or set lfs=0 "
850 "to ignore it)" % self._get_repo_url(ud))
791 851
792 def _get_repo_url(self, ud): 852 def _get_repo_url(self, ud):
793 """ 853 """
@@ -795,21 +855,21 @@ class Git(FetchMethod):
795 """ 855 """
796 # Note that we do not support passwords directly in the git urls. There are several 856 # Note that we do not support passwords directly in the git urls. There are several
797 # reasons. SRC_URI can be written out to things like buildhistory and people don't 857 # reasons. SRC_URI can be written out to things like buildhistory and people don't
798 # want to leak passwords like that. Its also all too easy to share metadata without 858 # want to leak passwords like that. Its also all too easy to share metadata without
799 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as 859 # removing the password. ssh keys, ~/.netrc and ~/.ssh/config files can be used as
800 # alternatives so we will not take patches adding password support here. 860 # alternatives so we will not take patches adding password support here.
801 if ud.user: 861 if ud.user:
802 username = ud.user + '@' 862 username = ud.user + '@'
803 else: 863 else:
804 username = "" 864 username = ""
805 return "%s://%s%s%s" % (ud.proto, username, ud.host, ud.path) 865 return "%s://%s%s%s" % (ud.proto, username, ud.host, urllib.parse.quote(ud.path))
806 866
807 def _revision_key(self, ud, d, name): 867 def _revision_key(self, ud, d, name):
808 """ 868 """
809 Return a unique key for the url 869 Return a unique key for the url
810 """ 870 """
811 # Collapse adjacent slashes 871 # Collapse adjacent slashes
812 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev[name] 872 return "git:" + ud.host + slash_re.sub(".", ud.path) + ud.unresolvedrev
813 873
814 def _lsremote(self, ud, d, search): 874 def _lsremote(self, ud, d, search):
815 """ 875 """
@@ -842,26 +902,26 @@ class Git(FetchMethod):
842 Compute the HEAD revision for the url 902 Compute the HEAD revision for the url
843 """ 903 """
844 if not d.getVar("__BBSRCREV_SEEN"): 904 if not d.getVar("__BBSRCREV_SEEN"):
845 raise bb.fetch2.FetchError("Recipe uses a floating tag/branch '%s' for repo '%s' without a fixed SRCREV yet doesn't call bb.fetch2.get_srcrev() (use SRCPV in PV for OE)." % (ud.unresolvedrev[name], ud.host+ud.path)) 905 raise bb.fetch2.FetchError("Recipe uses a floating tag/branch '%s' for repo '%s' without a fixed SRCREV yet doesn't call bb.fetch2.get_srcrev() (use SRCPV in PV for OE)." % (ud.unresolvedrev, ud.host+ud.path))
846 906
847 # Ensure we mark as not cached 907 # Ensure we mark as not cached
848 bb.fetch2.mark_recipe_nocache(d) 908 bb.fetch2.mark_recipe_nocache(d)
849 909
850 output = self._lsremote(ud, d, "") 910 output = self._lsremote(ud, d, "")
851 # Tags of the form ^{} may not work, need to fallback to other form 911 # Tags of the form ^{} may not work, need to fallback to other form
852 if ud.unresolvedrev[name][:5] == "refs/" or ud.usehead: 912 if ud.unresolvedrev[:5] == "refs/" or ud.usehead:
853 head = ud.unresolvedrev[name] 913 head = ud.unresolvedrev
854 tag = ud.unresolvedrev[name] 914 tag = ud.unresolvedrev
855 else: 915 else:
856 head = "refs/heads/%s" % ud.unresolvedrev[name] 916 head = "refs/heads/%s" % ud.unresolvedrev
857 tag = "refs/tags/%s" % ud.unresolvedrev[name] 917 tag = "refs/tags/%s" % ud.unresolvedrev
858 for s in [head, tag + "^{}", tag]: 918 for s in [head, tag + "^{}", tag]:
859 for l in output.strip().split('\n'): 919 for l in output.strip().split('\n'):
860 sha1, ref = l.split() 920 sha1, ref = l.split()
861 if s == ref: 921 if s == ref:
862 return sha1 922 return sha1
863 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \ 923 raise bb.fetch2.FetchError("Unable to resolve '%s' in upstream git repository in git ls-remote output for %s" % \
864 (ud.unresolvedrev[name], ud.host+ud.path)) 924 (ud.unresolvedrev, ud.host+ud.path))
865 925
866 def latest_versionstring(self, ud, d): 926 def latest_versionstring(self, ud, d):
867 """ 927 """
@@ -912,23 +972,22 @@ class Git(FetchMethod):
912 return pupver 972 return pupver
913 973
914 def _build_revision(self, ud, d, name): 974 def _build_revision(self, ud, d, name):
915 return ud.revisions[name] 975 return ud.revision
916 976
917 def gitpkgv_revision(self, ud, d, name): 977 def gitpkgv_revision(self, ud, d, name):
918 """ 978 """
919 Return a sortable revision number by counting commits in the history 979 Return a sortable revision number by counting commits in the history
920 Based on gitpkgv.bblass in meta-openembedded 980 Based on gitpkgv.bblass in meta-openembedded
921 """ 981 """
922 rev = self._build_revision(ud, d, name) 982 rev = ud.revision
923 localpath = ud.localpath 983 localpath = ud.localpath
924 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev) 984 rev_file = os.path.join(localpath, "oe-gitpkgv_" + rev)
925 if not os.path.exists(localpath): 985 if not os.path.exists(localpath):
926 commits = None 986 commits = None
927 else: 987 else:
928 if not os.path.exists(rev_file) or not os.path.getsize(rev_file): 988 if not os.path.exists(rev_file) or not os.path.getsize(rev_file):
929 from pipes import quote
930 commits = bb.fetch2.runfetchcmd( 989 commits = bb.fetch2.runfetchcmd(
931 "git rev-list %s -- | wc -l" % quote(rev), 990 "git rev-list %s -- | wc -l" % shlex.quote(rev),
932 d, quiet=True).strip().lstrip('0') 991 d, quiet=True).strip().lstrip('0')
933 if commits: 992 if commits:
934 open(rev_file, "w").write("%d\n" % int(commits)) 993 open(rev_file, "w").write("%d\n" % int(commits))
diff --git a/bitbake/lib/bb/fetch2/gitsm.py b/bitbake/lib/bb/fetch2/gitsm.py
index f7f3af7212..5869e1b99b 100644
--- a/bitbake/lib/bb/fetch2/gitsm.py
+++ b/bitbake/lib/bb/fetch2/gitsm.py
@@ -62,36 +62,35 @@ class GitSM(Git):
62 return modules 62 return modules
63 63
64 # Collect the defined submodules, and their attributes 64 # Collect the defined submodules, and their attributes
65 for name in ud.names: 65 try:
66 gitmodules = runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revision), d, quiet=True, workdir=workdir)
67 except:
68 # No submodules to update
69 gitmodules = ""
70
71 for m, md in parse_gitmodules(gitmodules).items():
66 try: 72 try:
67 gitmodules = runfetchcmd("%s show %s:.gitmodules" % (ud.basecmd, ud.revisions[name]), d, quiet=True, workdir=workdir) 73 module_hash = runfetchcmd("%s ls-tree -z -d %s %s" % (ud.basecmd, ud.revision, md['path']), d, quiet=True, workdir=workdir)
68 except: 74 except:
69 # No submodules to update 75 # If the command fails, we don't have a valid file to check. If it doesn't
76 # fail -- it still might be a failure, see next check...
77 module_hash = ""
78
79 if not module_hash:
80 logger.debug("submodule %s is defined, but is not initialized in the repository. Skipping", m)
70 continue 81 continue
71 82
72 for m, md in parse_gitmodules(gitmodules).items(): 83 submodules.append(m)
73 try: 84 paths[m] = md['path']
74 module_hash = runfetchcmd("%s ls-tree -z -d %s %s" % (ud.basecmd, ud.revisions[name], md['path']), d, quiet=True, workdir=workdir) 85 revision[m] = ud.revision
75 except: 86 uris[m] = md['url']
76 # If the command fails, we don't have a valid file to check. If it doesn't 87 subrevision[m] = module_hash.split()[2]
77 # fail -- it still might be a failure, see next check... 88
78 module_hash = "" 89 # Convert relative to absolute uri based on parent uri
79 90 if uris[m].startswith('..') or uris[m].startswith('./'):
80 if not module_hash: 91 newud = copy.copy(ud)
81 logger.debug("submodule %s is defined, but is not initialized in the repository. Skipping", m) 92 newud.path = os.path.normpath(os.path.join(newud.path, uris[m]))
82 continue 93 uris[m] = Git._get_repo_url(self, newud)
83
84 submodules.append(m)
85 paths[m] = md['path']
86 revision[m] = ud.revisions[name]
87 uris[m] = md['url']
88 subrevision[m] = module_hash.split()[2]
89
90 # Convert relative to absolute uri based on parent uri
91 if uris[m].startswith('..') or uris[m].startswith('./'):
92 newud = copy.copy(ud)
93 newud.path = os.path.normpath(os.path.join(newud.path, uris[m]))
94 uris[m] = Git._get_repo_url(self, newud)
95 94
96 for module in submodules: 95 for module in submodules:
97 # Translate the module url into a SRC_URI 96 # Translate the module url into a SRC_URI
@@ -123,7 +122,7 @@ class GitSM(Git):
123 url += ";name=%s" % module 122 url += ";name=%s" % module
124 url += ";subpath=%s" % module 123 url += ";subpath=%s" % module
125 url += ";nobranch=1" 124 url += ";nobranch=1"
126 url += ";lfs=%s" % self._need_lfs(ud) 125 url += ";lfs=%s" % ("1" if self._need_lfs(ud) else "0")
127 # Note that adding "user=" here to give credentials to the 126 # Note that adding "user=" here to give credentials to the
128 # submodule is not supported. Since using SRC_URI to give git:// 127 # submodule is not supported. Since using SRC_URI to give git://
129 # URL a password is not supported, one have to use one of the 128 # URL a password is not supported, one have to use one of the
@@ -147,6 +146,22 @@ class GitSM(Git):
147 146
148 return submodules != [] 147 return submodules != []
149 148
149 def call_process_submodules(self, ud, d, extra_check, subfunc):
150 # If we're using a shallow mirror tarball it needs to be
151 # unpacked temporarily so that we can examine the .gitmodules file
152 # Unpack even when ud.clonedir is not available,
153 # which may occur during a fast shallow clone
154 unpack = extra_check or not os.path.exists(ud.clonedir)
155 if ud.shallow and os.path.exists(ud.fullshallow) and unpack:
156 tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
157 try:
158 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
159 self.process_submodules(ud, tmpdir, subfunc, d)
160 finally:
161 shutil.rmtree(tmpdir)
162 else:
163 self.process_submodules(ud, ud.clonedir, subfunc, d)
164
150 def need_update(self, ud, d): 165 def need_update(self, ud, d):
151 if Git.need_update(self, ud, d): 166 if Git.need_update(self, ud, d):
152 return True 167 return True
@@ -164,15 +179,7 @@ class GitSM(Git):
164 logger.error('gitsm: submodule update check failed: %s %s' % (type(e).__name__, str(e))) 179 logger.error('gitsm: submodule update check failed: %s %s' % (type(e).__name__, str(e)))
165 need_update_result = True 180 need_update_result = True
166 181
167 # If we're using a shallow mirror tarball it needs to be unpacked 182 self.call_process_submodules(ud, d, not os.path.exists(ud.clonedir), need_update_submodule)
168 # temporarily so that we can examine the .gitmodules file
169 if ud.shallow and os.path.exists(ud.fullshallow) and not os.path.exists(ud.clonedir):
170 tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
171 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
172 self.process_submodules(ud, tmpdir, need_update_submodule, d)
173 shutil.rmtree(tmpdir)
174 else:
175 self.process_submodules(ud, ud.clonedir, need_update_submodule, d)
176 183
177 if need_update_list: 184 if need_update_list:
178 logger.debug('gitsm: Submodules requiring update: %s' % (' '.join(need_update_list))) 185 logger.debug('gitsm: Submodules requiring update: %s' % (' '.join(need_update_list)))
@@ -195,16 +202,7 @@ class GitSM(Git):
195 raise 202 raise
196 203
197 Git.download(self, ud, d) 204 Git.download(self, ud, d)
198 205 self.call_process_submodules(ud, d, self.need_update(ud, d), download_submodule)
199 # If we're using a shallow mirror tarball it needs to be unpacked
200 # temporarily so that we can examine the .gitmodules file
201 if ud.shallow and os.path.exists(ud.fullshallow) and self.need_update(ud, d):
202 tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
203 runfetchcmd("tar -xzf %s" % ud.fullshallow, d, workdir=tmpdir)
204 self.process_submodules(ud, tmpdir, download_submodule, d)
205 shutil.rmtree(tmpdir)
206 else:
207 self.process_submodules(ud, ud.clonedir, download_submodule, d)
208 206
209 def unpack(self, ud, destdir, d): 207 def unpack(self, ud, destdir, d):
210 def unpack_submodules(ud, url, module, modpath, workdir, d): 208 def unpack_submodules(ud, url, module, modpath, workdir, d):
@@ -247,15 +245,27 @@ class GitSM(Git):
247 ret = self.process_submodules(ud, ud.destdir, unpack_submodules, d) 245 ret = self.process_submodules(ud, ud.destdir, unpack_submodules, d)
248 246
249 if not ud.bareclone and ret: 247 if not ud.bareclone and ret:
250 # All submodules should already be downloaded and configured in the tree. This simply 248 cmdprefix = ""
251 # sets up the configuration and checks out the files. The main project config should 249 # Avoid LFS smudging (replacing the LFS pointers with the actual content) when LFS shouldn't be used but git-lfs is installed.
252 # remain unmodified, and no download from the internet should occur. As such, lfs smudge 250 if not self._need_lfs(ud):
253 # should also be skipped as these files were already smudged in the fetch stage if lfs 251 cmdprefix = "GIT_LFS_SKIP_SMUDGE=1 "
254 # was enabled. 252 runfetchcmd("%s%s submodule update --recursive --no-fetch" % (cmdprefix, ud.basecmd), d, quiet=True, workdir=ud.destdir)
255 runfetchcmd("GIT_LFS_SKIP_SMUDGE=1 %s submodule update --recursive --no-fetch" % (ud.basecmd), d, quiet=True, workdir=ud.destdir) 253 def clean(self, ud, d):
254 def clean_submodule(ud, url, module, modpath, workdir, d):
255 url += ";bareclone=1;nobranch=1"
256 try:
257 newfetch = Fetch([url], d, cache=False)
258 newfetch.clean()
259 except Exception as e:
260 logger.warning('gitsm: submodule clean failed: %s %s' % (type(e).__name__, str(e)))
261
262 self.call_process_submodules(ud, d, True, clean_submodule)
263
264 # Clean top git dir
265 Git.clean(self, ud, d)
256 266
257 def implicit_urldata(self, ud, d): 267 def implicit_urldata(self, ud, d):
258 import shutil, subprocess, tempfile 268 import subprocess
259 269
260 urldata = [] 270 urldata = []
261 def add_submodule(ud, url, module, modpath, workdir, d): 271 def add_submodule(ud, url, module, modpath, workdir, d):
@@ -263,14 +273,6 @@ class GitSM(Git):
263 newfetch = Fetch([url], d, cache=False) 273 newfetch = Fetch([url], d, cache=False)
264 urldata.extend(newfetch.expanded_urldata()) 274 urldata.extend(newfetch.expanded_urldata())
265 275
266 # If we're using a shallow mirror tarball it needs to be unpacked 276 self.call_process_submodules(ud, d, ud.method.need_update(ud, d), add_submodule)
267 # temporarily so that we can examine the .gitmodules file
268 if ud.shallow and os.path.exists(ud.fullshallow) and ud.method.need_update(ud, d):
269 tmpdir = tempfile.mkdtemp(dir=d.getVar("DL_DIR"))
270 subprocess.check_call("tar -xzf %s" % ud.fullshallow, cwd=tmpdir, shell=True)
271 self.process_submodules(ud, tmpdir, add_submodule, d)
272 shutil.rmtree(tmpdir)
273 else:
274 self.process_submodules(ud, ud.clonedir, add_submodule, d)
275 277
276 return urldata 278 return urldata
diff --git a/bitbake/lib/bb/fetch2/gomod.py b/bitbake/lib/bb/fetch2/gomod.py
new file mode 100644
index 0000000000..53c1d8d115
--- /dev/null
+++ b/bitbake/lib/bb/fetch2/gomod.py
@@ -0,0 +1,273 @@
1"""
2BitBake 'Fetch' implementation for Go modules
3
4The gomod/gomodgit fetchers are used to download Go modules to the module cache
5from a module proxy or directly from a version control repository.
6
7Example SRC_URI:
8
9SRC_URI += "gomod://golang.org/x/net;version=v0.9.0;sha256sum=..."
10SRC_URI += "gomodgit://golang.org/x/net;version=v0.9.0;repo=go.googlesource.com/net;srcrev=..."
11
12Required SRC_URI parameters:
13
14- version
15 The version of the module.
16
17Optional SRC_URI parameters:
18
19- mod
20 Fetch and unpack the go.mod file only instead of the complete module.
21 The go command may need to download go.mod files for many different modules
22 when computing the build list, and go.mod files are much smaller than
23 module zip files.
24 The default is "0", set mod=1 for the go.mod file only.
25
26- sha256sum
27 The checksum of the module zip file, or the go.mod file in case of fetching
28 only the go.mod file. Alternatively, set the SRC_URI varible flag for
29 "module@version.sha256sum".
30
31- protocol
32 The method used when fetching directly from a version control repository.
33 The default is "https" for git.
34
35- repo
36 The URL when fetching directly from a version control repository. Required
37 when the URL is different from the module path.
38
39- srcrev
40 The revision identifier used when fetching directly from a version control
41 repository. Alternatively, set the SRCREV varible for "module@version".
42
43- subdir
44 The module subdirectory when fetching directly from a version control
45 repository. Required when the module is not located in the root of the
46 repository.
47
48Related variables:
49
50- GO_MOD_PROXY
51 The module proxy used by the fetcher.
52
53- GO_MOD_CACHE_DIR
54 The directory where the module cache is located.
55 This must match the exported GOMODCACHE variable for the go command to find
56 the downloaded modules.
57
58See the Go modules reference, https://go.dev/ref/mod, for more information
59about the module cache, module proxies and version control systems.
60"""
61
62import hashlib
63import os
64import re
65import shutil
66import subprocess
67import zipfile
68
69import bb
70from bb.fetch2 import FetchError
71from bb.fetch2 import MissingParameterError
72from bb.fetch2 import runfetchcmd
73from bb.fetch2 import subprocess_setup
74from bb.fetch2.git import Git
75from bb.fetch2.wget import Wget
76
77
78def escape(path):
79 """Escape capital letters using exclamation points."""
80 return re.sub(r'([A-Z])', lambda m: '!' + m.group(1).lower(), path)
81
82
83class GoMod(Wget):
84 """Class to fetch Go modules from a Go module proxy via wget"""
85
86 def supports(self, ud, d):
87 """Check to see if a given URL is for this fetcher."""
88 return ud.type == 'gomod'
89
90 def urldata_init(self, ud, d):
91 """Set up to download the module from the module proxy.
92
93 Set up to download the module zip file to the module cache directory
94 and unpack the go.mod file (unless downloading only the go.mod file):
95
96 cache/download/<module>/@v/<version>.zip: The module zip file.
97 cache/download/<module>/@v/<version>.mod: The go.mod file.
98 """
99
100 proxy = d.getVar('GO_MOD_PROXY') or 'proxy.golang.org'
101 moddir = d.getVar('GO_MOD_CACHE_DIR') or 'pkg/mod'
102
103 if 'version' not in ud.parm:
104 raise MissingParameterError('version', ud.url)
105
106 module = ud.host
107 if ud.path != '/':
108 module += ud.path
109 ud.parm['module'] = module
110 version = ud.parm['version']
111
112 # Set URL and filename for wget download
113 if ud.parm.get('mod', '0') == '1':
114 ext = '.mod'
115 else:
116 ext = '.zip'
117 path = escape(f"{module}/@v/{version}{ext}")
118 ud.url = bb.fetch2.encodeurl(
119 ('https', proxy, '/' + path, None, None, None))
120 ud.parm['downloadfilename'] = f"{module.replace('/', '.')}@{version}{ext}"
121
122 # Set name for checksum verification
123 ud.parm['name'] = f"{module}@{version}"
124
125 # Set path for unpack
126 ud.parm['unpackpath'] = os.path.join(moddir, 'cache/download', path)
127
128 super().urldata_init(ud, d)
129
130 def unpack(self, ud, rootdir, d):
131 """Unpack the module in the module cache."""
132
133 # Unpack the module zip file or go.mod file
134 unpackpath = os.path.join(rootdir, ud.parm['unpackpath'])
135 unpackdir = os.path.dirname(unpackpath)
136 bb.utils.mkdirhier(unpackdir)
137 ud.unpack_tracer.unpack("file-copy", unpackdir)
138 cmd = f"cp {ud.localpath} {unpackpath}"
139 path = d.getVar('PATH')
140 if path:
141 cmd = f"PATH={path} {cmd}"
142 name = os.path.basename(unpackpath)
143 bb.note(f"Unpacking {name} to {unpackdir}/")
144 subprocess.check_call(cmd, shell=True, preexec_fn=subprocess_setup)
145
146 if name.endswith('.zip'):
147 # Unpack the go.mod file from the zip file
148 module = ud.parm['module']
149 name = name.rsplit('.', 1)[0] + '.mod'
150 bb.note(f"Unpacking {name} to {unpackdir}/")
151 with zipfile.ZipFile(ud.localpath) as zf:
152 with open(os.path.join(unpackdir, name), mode='wb') as mf:
153 try:
154 f = module + '@' + ud.parm['version'] + '/go.mod'
155 shutil.copyfileobj(zf.open(f), mf)
156 except KeyError:
157 # If the module does not have a go.mod file, synthesize
158 # one containing only a module statement.
159 mf.write(f'module {module}\n'.encode())
160
161
162class GoModGit(Git):
163 """Class to fetch Go modules directly from a git repository"""
164
165 def supports(self, ud, d):
166 """Check to see if a given URL is for this fetcher."""
167 return ud.type == 'gomodgit'
168
169 def urldata_init(self, ud, d):
170 """Set up to download the module from the git repository.
171
172 Set up to download the git repository to the module cache directory and
173 unpack the module zip file and the go.mod file:
174
175 cache/vcs/<hash>: The bare git repository.
176 cache/download/<module>/@v/<version>.zip: The module zip file.
177 cache/download/<module>/@v/<version>.mod: The go.mod file.
178 """
179
180 moddir = d.getVar('GO_MOD_CACHE_DIR') or 'pkg/mod'
181
182 if 'version' not in ud.parm:
183 raise MissingParameterError('version', ud.url)
184
185 module = ud.host
186 if ud.path != '/':
187 module += ud.path
188 ud.parm['module'] = module
189
190 # Set host, path and srcrev for git download
191 if 'repo' in ud.parm:
192 repo = ud.parm['repo']
193 idx = repo.find('/')
194 if idx != -1:
195 ud.host = repo[:idx]
196 ud.path = repo[idx:]
197 else:
198 ud.host = repo
199 ud.path = ''
200 if 'protocol' not in ud.parm:
201 ud.parm['protocol'] = 'https'
202 ud.name = f"{module}@{ud.parm['version']}"
203 srcrev = d.getVar('SRCREV_' + ud.name)
204 if srcrev:
205 if 'srcrev' not in ud.parm:
206 ud.parm['srcrev'] = srcrev
207 else:
208 if 'srcrev' in ud.parm:
209 d.setVar('SRCREV_' + ud.name, ud.parm['srcrev'])
210 if 'branch' not in ud.parm:
211 ud.parm['nobranch'] = '1'
212
213 # Set subpath, subdir and bareclone for git unpack
214 if 'subdir' in ud.parm:
215 ud.parm['subpath'] = ud.parm['subdir']
216 key = f"git3:{ud.parm['protocol']}://{ud.host}{ud.path}".encode()
217 ud.parm['key'] = key
218 ud.parm['subdir'] = os.path.join(moddir, 'cache/vcs',
219 hashlib.sha256(key).hexdigest())
220 ud.parm['bareclone'] = '1'
221
222 super().urldata_init(ud, d)
223
224 def unpack(self, ud, rootdir, d):
225 """Unpack the module in the module cache."""
226
227 # Unpack the bare git repository
228 super().unpack(ud, rootdir, d)
229
230 moddir = d.getVar('GO_MOD_CACHE_DIR') or 'pkg/mod'
231
232 # Create the info file
233 module = ud.parm['module']
234 repodir = os.path.join(rootdir, ud.parm['subdir'])
235 with open(repodir + '.info', 'wb') as f:
236 f.write(ud.parm['key'])
237
238 # Unpack the go.mod file from the repository
239 unpackdir = os.path.join(rootdir, moddir, 'cache/download',
240 escape(module), '@v')
241 bb.utils.mkdirhier(unpackdir)
242 srcrev = ud.parm['srcrev']
243 version = ud.parm['version']
244 escaped_version = escape(version)
245 cmd = f"git ls-tree -r --name-only '{srcrev}'"
246 if 'subpath' in ud.parm:
247 cmd += f" '{ud.parm['subpath']}'"
248 files = runfetchcmd(cmd, d, workdir=repodir).split()
249 name = escaped_version + '.mod'
250 bb.note(f"Unpacking {name} to {unpackdir}/")
251 with open(os.path.join(unpackdir, name), mode='wb') as mf:
252 f = 'go.mod'
253 if 'subpath' in ud.parm:
254 f = os.path.join(ud.parm['subpath'], f)
255 if f in files:
256 cmd = ['git', 'cat-file', 'blob', srcrev + ':' + f]
257 subprocess.check_call(cmd, stdout=mf, cwd=repodir,
258 preexec_fn=subprocess_setup)
259 else:
260 # If the module does not have a go.mod file, synthesize one
261 # containing only a module statement.
262 mf.write(f'module {module}\n'.encode())
263
264 # Synthesize the module zip file from the repository
265 name = escaped_version + '.zip'
266 bb.note(f"Unpacking {name} to {unpackdir}/")
267 with zipfile.ZipFile(os.path.join(unpackdir, name), mode='w') as zf:
268 prefix = module + '@' + version + '/'
269 for f in files:
270 cmd = ['git', 'cat-file', 'blob', srcrev + ':' + f]
271 data = subprocess.check_output(cmd, cwd=repodir,
272 preexec_fn=subprocess_setup)
273 zf.writestr(prefix + f, data)
diff --git a/bitbake/lib/bb/fetch2/local.py b/bitbake/lib/bb/fetch2/local.py
index 7d7668110e..fda56a564e 100644
--- a/bitbake/lib/bb/fetch2/local.py
+++ b/bitbake/lib/bb/fetch2/local.py
@@ -29,11 +29,10 @@ class Local(FetchMethod):
29 29
30 def urldata_init(self, ud, d): 30 def urldata_init(self, ud, d):
31 # We don't set localfile as for this fetcher the file is already local! 31 # We don't set localfile as for this fetcher the file is already local!
32 ud.decodedurl = urllib.parse.unquote(ud.url.split("://")[1].split(";")[0]) 32 ud.basename = os.path.basename(ud.path)
33 ud.basename = os.path.basename(ud.decodedurl) 33 ud.basepath = ud.path
34 ud.basepath = ud.decodedurl
35 ud.needdonestamp = False 34 ud.needdonestamp = False
36 if "*" in ud.decodedurl: 35 if "*" in ud.path:
37 raise bb.fetch2.ParameterError("file:// urls using globbing are no longer supported. Please place the files in a directory and reference that instead.", ud.url) 36 raise bb.fetch2.ParameterError("file:// urls using globbing are no longer supported. Please place the files in a directory and reference that instead.", ud.url)
38 return 37 return
39 38
@@ -48,7 +47,7 @@ class Local(FetchMethod):
48 Return the local filename of a given url assuming a successful fetch. 47 Return the local filename of a given url assuming a successful fetch.
49 """ 48 """
50 searched = [] 49 searched = []
51 path = urldata.decodedurl 50 path = urldata.path
52 newpath = path 51 newpath = path
53 if path[0] == "/": 52 if path[0] == "/":
54 logger.debug2("Using absolute %s" % (path)) 53 logger.debug2("Using absolute %s" % (path))
diff --git a/bitbake/lib/bb/fetch2/npm.py b/bitbake/lib/bb/fetch2/npm.py
index 15f3f19bc8..e469d66768 100644
--- a/bitbake/lib/bb/fetch2/npm.py
+++ b/bitbake/lib/bb/fetch2/npm.py
@@ -42,11 +42,12 @@ from bb.utils import is_semver
42 42
43def npm_package(package): 43def npm_package(package):
44 """Convert the npm package name to remove unsupported character""" 44 """Convert the npm package name to remove unsupported character"""
45 # Scoped package names (with the @) use the same naming convention 45 # For scoped package names ('@user/package') the '/' is replaced by a '-'.
46 # as the 'npm pack' command. 46 # This is similar to what 'npm pack' does, but 'npm pack' also strips the
47 # leading '@', which can lead to ambiguous package names.
47 name = re.sub("/", "-", package) 48 name = re.sub("/", "-", package)
48 name = name.lower() 49 name = name.lower()
49 name = re.sub(r"[^\-a-z0-9]", "", name) 50 name = re.sub(r"[^\-a-z0-9@]", "", name)
50 name = name.strip("-") 51 name = name.strip("-")
51 return name 52 return name
52 53
@@ -90,6 +91,12 @@ class NpmEnvironment(object):
90 self.d = d 91 self.d = d
91 92
92 self.user_config = tempfile.NamedTemporaryFile(mode="w", buffering=1) 93 self.user_config = tempfile.NamedTemporaryFile(mode="w", buffering=1)
94
95 hn = self._home_npmrc(d)
96 if hn is not None:
97 with open(hn, 'r') as hnf:
98 self.user_config.write(hnf.read())
99
93 for key, value in configs: 100 for key, value in configs:
94 self.user_config.write("%s=%s\n" % (key, value)) 101 self.user_config.write("%s=%s\n" % (key, value))
95 102
@@ -102,6 +109,15 @@ class NpmEnvironment(object):
102 if self.user_config: 109 if self.user_config:
103 self.user_config.close() 110 self.user_config.close()
104 111
112 def _home_npmrc(self, d):
113 """Function to return user's HOME .npmrc file (or None if it doesn't exist)"""
114 home_npmrc_file = os.path.join(os.environ.get("HOME"), ".npmrc")
115 if d.getVar("BB_USE_HOME_NPMRC") == "1" and os.path.exists(home_npmrc_file):
116 bb.warn(f"BB_USE_HOME_NPMRC flag set and valid .npmrc detected - "\
117 f"npm fetcher will use {home_npmrc_file}")
118 return home_npmrc_file
119 return None
120
105 def run(self, cmd, args=None, configs=None, workdir=None): 121 def run(self, cmd, args=None, configs=None, workdir=None):
106 """Run npm command in a controlled environment""" 122 """Run npm command in a controlled environment"""
107 with tempfile.TemporaryDirectory() as tmpdir: 123 with tempfile.TemporaryDirectory() as tmpdir:
@@ -165,7 +181,7 @@ class Npm(FetchMethod):
165 # Using the 'downloadfilename' parameter as local filename 181 # Using the 'downloadfilename' parameter as local filename
166 # or the npm package name. 182 # or the npm package name.
167 if "downloadfilename" in ud.parm: 183 if "downloadfilename" in ud.parm:
168 ud.localfile = npm_localfile(d.expand(ud.parm["downloadfilename"])) 184 ud.localfile = npm_localfile(ud.parm["downloadfilename"])
169 else: 185 else:
170 ud.localfile = npm_localfile(ud.package, ud.version) 186 ud.localfile = npm_localfile(ud.package, ud.version)
171 187
diff --git a/bitbake/lib/bb/fetch2/npmsw.py b/bitbake/lib/bb/fetch2/npmsw.py
index ff5f8dc755..2f9599ee9e 100644
--- a/bitbake/lib/bb/fetch2/npmsw.py
+++ b/bitbake/lib/bb/fetch2/npmsw.py
@@ -37,38 +37,26 @@ def foreach_dependencies(shrinkwrap, callback=None, dev=False):
37 """ 37 """
38 Run a callback for each dependencies of a shrinkwrap file. 38 Run a callback for each dependencies of a shrinkwrap file.
39 The callback is using the format: 39 The callback is using the format:
40 callback(name, params, deptree) 40 callback(name, data, location)
41 with: 41 with:
42 name = the package name (string) 42 name = the package name (string)
43 params = the package parameters (dictionary) 43 data = the package data (dictionary)
44 destdir = the destination of the package (string) 44 location = the location of the package (string)
45 """ 45 """
46 # For handling old style dependencies entries in shinkwrap files 46 packages = shrinkwrap.get("packages")
47 def _walk_deps(deps, deptree): 47 if not packages:
48 for name in deps: 48 raise FetchError("Invalid shrinkwrap file format")
49 subtree = [*deptree, name] 49
50 _walk_deps(deps[name].get("dependencies", {}), subtree) 50 for location, data in packages.items():
51 if callback is not None: 51 # Skip empty main and local link target packages
52 if deps[name].get("dev", False) and not dev: 52 if not location.startswith('node_modules/'):
53 continue 53 continue
54 elif deps[name].get("bundled", False): 54 elif not dev and data.get("dev", False):
55 continue 55 continue
56 destsubdirs = [os.path.join("node_modules", dep) for dep in subtree] 56 elif data.get("inBundle", False):
57 destsuffix = os.path.join(*destsubdirs) 57 continue
58 callback(name, deps[name], destsuffix) 58 name = location.split('node_modules/')[-1]
59 59 callback(name, data, location)
60 # packages entry means new style shrinkwrap file, else use dependencies
61 packages = shrinkwrap.get("packages", None)
62 if packages is not None:
63 for package in packages:
64 if package != "":
65 name = package.split('node_modules/')[-1]
66 package_infos = packages.get(package, {})
67 if dev == False and package_infos.get("dev", False):
68 continue
69 callback(name, package_infos, package)
70 else:
71 _walk_deps(shrinkwrap.get("dependencies", {}), [])
72 60
73class NpmShrinkWrap(FetchMethod): 61class NpmShrinkWrap(FetchMethod):
74 """Class to fetch all package from a shrinkwrap file""" 62 """Class to fetch all package from a shrinkwrap file"""
@@ -95,12 +83,18 @@ class NpmShrinkWrap(FetchMethod):
95 extrapaths = [] 83 extrapaths = []
96 unpack = True 84 unpack = True
97 85
98 integrity = params.get("integrity", None) 86 integrity = params.get("integrity")
99 resolved = params.get("resolved", None) 87 resolved = params.get("resolved")
100 version = params.get("version", None) 88 version = params.get("version")
89 link = params.get("link", False)
90
91 # Handle link sources
92 if link:
93 localpath = resolved
94 unpack = False
101 95
102 # Handle registry sources 96 # Handle registry sources
103 if is_semver(version) and integrity: 97 elif version and is_semver(version) and integrity:
104 # Handle duplicate dependencies without url 98 # Handle duplicate dependencies without url
105 if not resolved: 99 if not resolved:
106 return 100 return
@@ -128,10 +122,10 @@ class NpmShrinkWrap(FetchMethod):
128 extrapaths.append(resolvefile) 122 extrapaths.append(resolvefile)
129 123
130 # Handle http tarball sources 124 # Handle http tarball sources
131 elif version.startswith("http") and integrity: 125 elif resolved.startswith("http") and integrity:
132 localfile = npm_localfile(os.path.basename(version)) 126 localfile = npm_localfile(os.path.basename(resolved))
133 127
134 uri = URI(version) 128 uri = URI(resolved)
135 uri.params["downloadfilename"] = localfile 129 uri.params["downloadfilename"] = localfile
136 130
137 checksum_name, checksum_expected = npm_integrity(integrity) 131 checksum_name, checksum_expected = npm_integrity(integrity)
@@ -141,28 +135,12 @@ class NpmShrinkWrap(FetchMethod):
141 135
142 localpath = os.path.join(d.getVar("DL_DIR"), localfile) 136 localpath = os.path.join(d.getVar("DL_DIR"), localfile)
143 137
144 # Handle local tarball and link sources 138 # Handle local tarball sources
145 elif version.startswith("file"): 139 elif resolved.startswith("file"):
146 localpath = version[5:] 140 localpath = resolved[5:]
147 if not version.endswith(".tgz"):
148 unpack = False
149 141
150 # Handle git sources 142 # Handle git sources
151 elif version.startswith(("git", "bitbucket","gist")) or ( 143 elif resolved.startswith("git"):
152 not version.endswith((".tgz", ".tar", ".tar.gz"))
153 and not version.startswith((".", "@", "/"))
154 and "/" in version
155 ):
156 if version.startswith("github:"):
157 version = "git+https://github.com/" + version[len("github:"):]
158 elif version.startswith("gist:"):
159 version = "git+https://gist.github.com/" + version[len("gist:"):]
160 elif version.startswith("bitbucket:"):
161 version = "git+https://bitbucket.org/" + version[len("bitbucket:"):]
162 elif version.startswith("gitlab:"):
163 version = "git+https://gitlab.com/" + version[len("gitlab:"):]
164 elif not version.startswith(("git+","git:")):
165 version = "git+https://github.com/" + version
166 regex = re.compile(r""" 144 regex = re.compile(r"""
167 ^ 145 ^
168 git\+ 146 git\+
@@ -174,16 +152,16 @@ class NpmShrinkWrap(FetchMethod):
174 $ 152 $
175 """, re.VERBOSE) 153 """, re.VERBOSE)
176 154
177 match = regex.match(version) 155 match = regex.match(resolved)
178
179 if not match: 156 if not match:
180 raise ParameterError("Invalid git url: %s" % version, ud.url) 157 raise ParameterError("Invalid git url: %s" % resolved, ud.url)
181 158
182 groups = match.groupdict() 159 groups = match.groupdict()
183 160
184 uri = URI("git://" + str(groups["url"])) 161 uri = URI("git://" + str(groups["url"]))
185 uri.params["protocol"] = str(groups["protocol"]) 162 uri.params["protocol"] = str(groups["protocol"])
186 uri.params["rev"] = str(groups["rev"]) 163 uri.params["rev"] = str(groups["rev"])
164 uri.params["nobranch"] = "1"
187 uri.params["destsuffix"] = destsuffix 165 uri.params["destsuffix"] = destsuffix
188 166
189 url = str(uri) 167 url = str(uri)
@@ -268,7 +246,7 @@ class NpmShrinkWrap(FetchMethod):
268 246
269 def unpack(self, ud, rootdir, d): 247 def unpack(self, ud, rootdir, d):
270 """Unpack the downloaded dependencies""" 248 """Unpack the downloaded dependencies"""
271 destdir = d.getVar("S") 249 destdir = rootdir
272 destsuffix = ud.parm.get("destsuffix") 250 destsuffix = ud.parm.get("destsuffix")
273 if destsuffix: 251 if destsuffix:
274 destdir = os.path.join(rootdir, destsuffix) 252 destdir = os.path.join(rootdir, destsuffix)
diff --git a/bitbake/lib/bb/fetch2/s3.py b/bitbake/lib/bb/fetch2/s3.py
index 6b8ffd5359..22c0538139 100644
--- a/bitbake/lib/bb/fetch2/s3.py
+++ b/bitbake/lib/bb/fetch2/s3.py
@@ -77,7 +77,7 @@ class S3(FetchMethod):
77 else: 77 else:
78 ud.basename = os.path.basename(ud.path) 78 ud.basename = os.path.basename(ud.path)
79 79
80 ud.localfile = d.expand(urllib.parse.unquote(ud.basename)) 80 ud.localfile = ud.basename
81 81
82 ud.basecmd = d.getVar("FETCHCMD_s3") or "/usr/bin/env aws s3" 82 ud.basecmd = d.getVar("FETCHCMD_s3") or "/usr/bin/env aws s3"
83 83
diff --git a/bitbake/lib/bb/fetch2/sftp.py b/bitbake/lib/bb/fetch2/sftp.py
index 7884cce949..bee71a0d0d 100644
--- a/bitbake/lib/bb/fetch2/sftp.py
+++ b/bitbake/lib/bb/fetch2/sftp.py
@@ -77,7 +77,7 @@ class SFTP(FetchMethod):
77 else: 77 else:
78 ud.basename = os.path.basename(ud.path) 78 ud.basename = os.path.basename(ud.path)
79 79
80 ud.localfile = d.expand(urllib.parse.unquote(ud.basename)) 80 ud.localfile = ud.basename
81 81
82 def download(self, ud, d): 82 def download(self, ud, d):
83 """Fetch urls""" 83 """Fetch urls"""
diff --git a/bitbake/lib/bb/fetch2/ssh.py b/bitbake/lib/bb/fetch2/ssh.py
index 0cbb2a6f25..2a0f2cb44b 100644
--- a/bitbake/lib/bb/fetch2/ssh.py
+++ b/bitbake/lib/bb/fetch2/ssh.py
@@ -73,8 +73,7 @@ class SSH(FetchMethod):
73 path = m.group('path') 73 path = m.group('path')
74 path = urllib.parse.unquote(path) 74 path = urllib.parse.unquote(path)
75 host = m.group('host') 75 host = m.group('host')
76 urldata.localpath = os.path.join(d.getVar('DL_DIR'), 76 urldata.localfile = os.path.basename(os.path.normpath(path))
77 os.path.basename(os.path.normpath(path)))
78 77
79 def download(self, urldata, d): 78 def download(self, urldata, d):
80 dldir = d.getVar('DL_DIR') 79 dldir = d.getVar('DL_DIR')
diff --git a/bitbake/lib/bb/fetch2/svn.py b/bitbake/lib/bb/fetch2/svn.py
index d40e4d2909..0852108e7d 100644
--- a/bitbake/lib/bb/fetch2/svn.py
+++ b/bitbake/lib/bb/fetch2/svn.py
@@ -210,3 +210,6 @@ class Svn(FetchMethod):
210 210
211 def _build_revision(self, ud, d): 211 def _build_revision(self, ud, d):
212 return ud.revision 212 return ud.revision
213
214 def supports_checksum(self, urldata):
215 return False
diff --git a/bitbake/lib/bb/fetch2/wget.py b/bitbake/lib/bb/fetch2/wget.py
index fbfa6938ac..7e43d3bc97 100644
--- a/bitbake/lib/bb/fetch2/wget.py
+++ b/bitbake/lib/bb/fetch2/wget.py
@@ -53,11 +53,6 @@ class WgetProgressHandler(bb.progress.LineFilterProgressHandler):
53class Wget(FetchMethod): 53class Wget(FetchMethod):
54 """Class to fetch urls via 'wget'""" 54 """Class to fetch urls via 'wget'"""
55 55
56 # CDNs like CloudFlare may do a 'browser integrity test' which can fail
57 # with the standard wget/urllib User-Agent, so pretend to be a modern
58 # browser.
59 user_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0"
60
61 def check_certs(self, d): 56 def check_certs(self, d):
62 """ 57 """
63 Should certificates be checked? 58 Should certificates be checked?
@@ -83,11 +78,11 @@ class Wget(FetchMethod):
83 else: 78 else:
84 ud.basename = os.path.basename(ud.path) 79 ud.basename = os.path.basename(ud.path)
85 80
86 ud.localfile = d.expand(urllib.parse.unquote(ud.basename)) 81 ud.localfile = ud.basename
87 if not ud.localfile: 82 if not ud.localfile:
88 ud.localfile = d.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", ".")) 83 ud.localfile = ud.host + ud.path.replace("/", ".")
89 84
90 self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget -t 2 -T 30" 85 self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget --tries=2 --timeout=100"
91 86
92 if ud.type == 'ftp' or ud.type == 'ftps': 87 if ud.type == 'ftp' or ud.type == 'ftps':
93 self.basecmd += " --passive-ftp" 88 self.basecmd += " --passive-ftp"
@@ -101,16 +96,17 @@ class Wget(FetchMethod):
101 96
102 logger.debug2("Fetching %s using command '%s'" % (ud.url, command)) 97 logger.debug2("Fetching %s using command '%s'" % (ud.url, command))
103 bb.fetch2.check_network_access(d, command, ud.url) 98 bb.fetch2.check_network_access(d, command, ud.url)
104 runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler, workdir=workdir) 99 runfetchcmd(command + ' --progress=dot --verbose', d, quiet, log=progresshandler, workdir=workdir)
105 100
106 def download(self, ud, d): 101 def download(self, ud, d):
107 """Fetch urls""" 102 """Fetch urls"""
108 103
109 fetchcmd = self.basecmd 104 fetchcmd = self.basecmd
110 105
111 localpath = os.path.join(d.getVar("DL_DIR"), ud.localfile) + ".tmp" 106 dldir = os.path.realpath(d.getVar("DL_DIR"))
107 localpath = os.path.join(dldir, ud.localfile) + ".tmp"
112 bb.utils.mkdirhier(os.path.dirname(localpath)) 108 bb.utils.mkdirhier(os.path.dirname(localpath))
113 fetchcmd += " -O %s" % shlex.quote(localpath) 109 fetchcmd += " --output-document=%s" % shlex.quote(localpath)
114 110
115 if ud.user and ud.pswd: 111 if ud.user and ud.pswd:
116 fetchcmd += " --auth-no-challenge" 112 fetchcmd += " --auth-no-challenge"
@@ -126,14 +122,18 @@ class Wget(FetchMethod):
126 fetchcmd += " --user=%s --password=%s" % (ud.user, ud.pswd) 122 fetchcmd += " --user=%s --password=%s" % (ud.user, ud.pswd)
127 123
128 uri = ud.url.split(";")[0] 124 uri = ud.url.split(";")[0]
129 if os.path.exists(ud.localpath): 125 fetchcmd += " --continue --directory-prefix=%s '%s'" % (dldir, uri)
130 # file exists, but we didnt complete it.. trying again..
131 fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
132 else:
133 fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
134
135 self._runwget(ud, d, fetchcmd, False) 126 self._runwget(ud, d, fetchcmd, False)
136 127
128 # Sanity check since wget can pretend it succeed when it didn't
129 # Also, this used to happen if sourceforge sent us to the mirror page
130 if not os.path.exists(localpath):
131 raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, localpath), uri)
132
133 if os.path.getsize(localpath) == 0:
134 os.remove(localpath)
135 raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
136
137 # Try and verify any checksum now, meaning if it isn't correct, we don't remove the 137 # Try and verify any checksum now, meaning if it isn't correct, we don't remove the
138 # original file, which might be a race (imagine two recipes referencing the same 138 # original file, which might be a race (imagine two recipes referencing the same
139 # source, one with an incorrect checksum) 139 # source, one with an incorrect checksum)
@@ -143,15 +143,6 @@ class Wget(FetchMethod):
143 # Our lock prevents multiple writers but mirroring code may grab incomplete files 143 # Our lock prevents multiple writers but mirroring code may grab incomplete files
144 os.rename(localpath, localpath[:-4]) 144 os.rename(localpath, localpath[:-4])
145 145
146 # Sanity check since wget can pretend it succeed when it didn't
147 # Also, this used to happen if sourceforge sent us to the mirror page
148 if not os.path.exists(ud.localpath):
149 raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
150
151 if os.path.getsize(ud.localpath) == 0:
152 os.remove(ud.localpath)
153 raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
154
155 return True 146 return True
156 147
157 def checkstatus(self, fetch, ud, d, try_again=True): 148 def checkstatus(self, fetch, ud, d, try_again=True):
@@ -243,7 +234,12 @@ class Wget(FetchMethod):
243 fetch.connection_cache.remove_connection(h.host, h.port) 234 fetch.connection_cache.remove_connection(h.host, h.port)
244 raise urllib.error.URLError(err) 235 raise urllib.error.URLError(err)
245 else: 236 else:
246 r = h.getresponse() 237 try:
238 r = h.getresponse()
239 except TimeoutError as e:
240 if fetch.connection_cache:
241 fetch.connection_cache.remove_connection(h.host, h.port)
242 raise TimeoutError(e)
247 243
248 # Pick apart the HTTPResponse object to get the addinfourl 244 # Pick apart the HTTPResponse object to get the addinfourl
249 # object initialized properly. 245 # object initialized properly.
@@ -304,13 +300,45 @@ class Wget(FetchMethod):
304 300
305 class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler): 301 class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
306 """ 302 """
307 urllib2.HTTPRedirectHandler resets the method to GET on redirect, 303 urllib2.HTTPRedirectHandler before 3.13 has two flaws:
308 when we want to follow redirects using the original method. 304
305 It resets the method to GET on redirect when we want to follow
306 redirects using the original method (typically HEAD). This was fixed
307 in 759e8e7.
308
309 It also doesn't handle 308 (Permanent Redirect). This was fixed in
310 c379bc5.
311
312 Until we depend on Python 3.13 onwards, copy the redirect_request
313 method to fix these issues.
309 """ 314 """
310 def redirect_request(self, req, fp, code, msg, headers, newurl): 315 def redirect_request(self, req, fp, code, msg, headers, newurl):
311 newreq = urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) 316 m = req.get_method()
312 newreq.get_method = req.get_method 317 if (not (code in (301, 302, 303, 307, 308) and m in ("GET", "HEAD")
313 return newreq 318 or code in (301, 302, 303) and m == "POST")):
319 raise urllib.HTTPError(req.full_url, code, msg, headers, fp)
320
321 # Strictly (according to RFC 2616), 301 or 302 in response to
322 # a POST MUST NOT cause a redirection without confirmation
323 # from the user (of urllib.request, in this case). In practice,
324 # essentially all clients do redirect in this case, so we do
325 # the same.
326
327 # Be conciliant with URIs containing a space. This is mainly
328 # redundant with the more complete encoding done in http_error_302(),
329 # but it is kept for compatibility with other callers.
330 newurl = newurl.replace(' ', '%20')
331
332 CONTENT_HEADERS = ("content-length", "content-type")
333 newheaders = {k: v for k, v in req.headers.items()
334 if k.lower() not in CONTENT_HEADERS}
335 return urllib.request.Request(newurl,
336 method="HEAD" if m == "HEAD" else "GET",
337 headers=newheaders,
338 origin_req_host=req.origin_req_host,
339 unverifiable=True)
340
341 http_error_308 = urllib.request.HTTPRedirectHandler.http_error_302
314 342
315 # We need to update the environment here as both the proxy and HTTPS 343 # We need to update the environment here as both the proxy and HTTPS
316 # handlers need variables set. The proxy needs http_proxy and friends to 344 # handlers need variables set. The proxy needs http_proxy and friends to
@@ -343,14 +371,14 @@ class Wget(FetchMethod):
343 opener = urllib.request.build_opener(*handlers) 371 opener = urllib.request.build_opener(*handlers)
344 372
345 try: 373 try:
346 uri_base = ud.url.split(";")[0] 374 parts = urllib.parse.urlparse(ud.url.split(";")[0])
347 uri = "{}://{}{}".format(urllib.parse.urlparse(uri_base).scheme, ud.host, ud.path) 375 uri = "{}://{}{}".format(parts.scheme, parts.netloc, parts.path)
348 r = urllib.request.Request(uri) 376 r = urllib.request.Request(uri)
349 r.get_method = lambda: "HEAD" 377 r.get_method = lambda: "HEAD"
350 # Some servers (FusionForge, as used on Alioth) require that the 378 # Some servers (FusionForge, as used on Alioth) require that the
351 # optional Accept header is set. 379 # optional Accept header is set.
352 r.add_header("Accept", "*/*") 380 r.add_header("Accept", "*/*")
353 r.add_header("User-Agent", self.user_agent) 381 r.add_header("User-Agent", "bitbake/{}".format(bb.__version__))
354 def add_basic_auth(login_str, request): 382 def add_basic_auth(login_str, request):
355 '''Adds Basic auth to http request, pass in login:password as string''' 383 '''Adds Basic auth to http request, pass in login:password as string'''
356 import base64 384 import base64
@@ -370,7 +398,7 @@ class Wget(FetchMethod):
370 except (FileNotFoundError, netrc.NetrcParseError): 398 except (FileNotFoundError, netrc.NetrcParseError):
371 pass 399 pass
372 400
373 with opener.open(r, timeout=30) as response: 401 with opener.open(r, timeout=100) as response:
374 pass 402 pass
375 except (urllib.error.URLError, ConnectionResetError, TimeoutError) as e: 403 except (urllib.error.URLError, ConnectionResetError, TimeoutError) as e:
376 if try_again: 404 if try_again:
@@ -457,7 +485,7 @@ class Wget(FetchMethod):
457 f = tempfile.NamedTemporaryFile() 485 f = tempfile.NamedTemporaryFile()
458 with tempfile.TemporaryDirectory(prefix="wget-index-") as workdir, tempfile.NamedTemporaryFile(dir=workdir, prefix="wget-listing-") as f: 486 with tempfile.TemporaryDirectory(prefix="wget-index-") as workdir, tempfile.NamedTemporaryFile(dir=workdir, prefix="wget-listing-") as f:
459 fetchcmd = self.basecmd 487 fetchcmd = self.basecmd
460 fetchcmd += " -O " + f.name + " --user-agent='" + self.user_agent + "' '" + uri + "'" 488 fetchcmd += " --output-document=%s '%s'" % (f.name, uri)
461 try: 489 try:
462 self._runwget(ud, d, fetchcmd, True, workdir=workdir) 490 self._runwget(ud, d, fetchcmd, True, workdir=workdir)
463 fetchresult = f.read() 491 fetchresult = f.read()
@@ -617,13 +645,17 @@ class Wget(FetchMethod):
617 645
618 sanity check to ensure same name and type. 646 sanity check to ensure same name and type.
619 """ 647 """
620 package = ud.path.split("/")[-1] 648 if 'downloadfilename' in ud.parm:
649 package = ud.parm['downloadfilename']
650 else:
651 package = ud.path.split("/")[-1]
621 current_version = ['', d.getVar('PV'), ''] 652 current_version = ['', d.getVar('PV'), '']
622 653
623 """possible to have no version in pkg name, such as spectrum-fw""" 654 """possible to have no version in pkg name, such as spectrum-fw"""
624 if not re.search(r"\d+", package): 655 if not re.search(r"\d+", package):
625 current_version[1] = re.sub('_', '.', current_version[1]) 656 current_version[1] = re.sub('_', '.', current_version[1])
626 current_version[1] = re.sub('-', '.', current_version[1]) 657 current_version[1] = re.sub('-', '.', current_version[1])
658 bb.debug(3, "latest_versionstring: no version found in %s" % package)
627 return (current_version[1], '') 659 return (current_version[1], '')
628 660
629 package_regex = self._init_regexes(package, ud, d) 661 package_regex = self._init_regexes(package, ud, d)