diff options
Diffstat (limited to 'bitbake/lib/bb/fetch2')
-rw-r--r-- | bitbake/lib/bb/fetch2/__init__.py | 224 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/az.py | 9 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/clearcase.py | 6 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/crate.py | 9 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/gcp.py | 15 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/git.py | 459 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/gitsm.py | 126 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/gomod.py | 273 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/local.py | 9 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/npm.py | 24 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/npmsw.py | 98 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/s3.py | 2 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/sftp.py | 2 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/ssh.py | 3 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/svn.py | 3 | ||||
-rw-r--r-- | bitbake/lib/bb/fetch2/wget.py | 108 |
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 | |||
23 | import subprocess | 23 | import subprocess |
24 | import pickle | 24 | import pickle |
25 | import errno | 25 | import errno |
26 | import bb.persist_data, bb.utils | 26 | import bb.utils |
27 | import bb.checksum | 27 | import bb.checksum |
28 | import bb.process | 28 | import bb.process |
29 | import bb.event | 29 | import 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 | ||
34 | logger = logging.getLogger("BitBake.Fetcher") | 35 | logger = logging.getLogger("BitBake.Fetcher") |
35 | 36 | ||
36 | CHECKSUM_LIST = [ "md5", "sha256", "sha1", "sha384", "sha512" ] | 37 | CHECKSUM_LIST = [ "goh1", "md5", "sha256", "sha1", "sha384", "sha512" ] |
37 | SHOWN_CHECKSUM_LIST = ["sha256"] | 38 | SHOWN_CHECKSUM_LIST = ["sha256"] |
38 | 39 | ||
39 | class BBFetchException(Exception): | 40 | class 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 | |||
360 | def 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 | ||
424 | def uri_replace(ud, uri_find, uri_replace, replacements, d, mirrortarball=None): | 438 | def 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 = [] | |||
493 | urldata_cache = {} | 507 | urldata_cache = {} |
494 | saved_headrevs = {} | 508 | saved_headrevs = {} |
495 | 509 | ||
496 | def fetcher_init(d): | 510 | def 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 | ||
527 | def fetcher_parse_save(): | 545 | def fetcher_parse_save(): |
528 | _checksum_cache.save_extras() | 546 | _checksum_cache.save_extras() |
547 | _revisions_cache.save_extras() | ||
529 | 548 | ||
530 | def fetcher_parse_done(): | 549 | def fetcher_parse_done(): |
531 | _checksum_cache.save_merge() | 550 | _checksum_cache.save_merge() |
551 | _revisions_cache.save_merge() | ||
532 | 552 | ||
533 | def fetcher_compare_revisions(d): | 553 | def 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 | ||
542 | def mirror_from_string(data): | 562 | def 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 | ||
883 | def get_fetcher_environment(d): | 903 | def 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 | |||
1131 | def 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 | |||
1149 | def try_mirrors(fetch, d, origud, mirrors, check = False): | 1157 | def 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 | |||
2088 | from . import az | 2117 | from . import az |
2089 | from . import crate | 2118 | from . import crate |
2090 | from . import gcp | 2119 | from . import gcp |
2120 | from . import gomod | ||
2091 | 2121 | ||
2092 | methods.append(local.Local()) | 2122 | methods.append(local.Local()) |
2093 | methods.append(wget.Wget()) | 2123 | methods.append(wget.Wget()) |
@@ -2110,3 +2140,5 @@ methods.append(npmsw.NpmShrinkWrap()) | |||
2110 | methods.append(az.Az()) | 2140 | methods.append(az.Az()) |
2111 | methods.append(crate.Crate()) | 2141 | methods.append(crate.Crate()) |
2112 | methods.append(gcp.GCP()) | 2142 | methods.append(gcp.GCP()) |
2143 | methods.append(gomod.GoMod()) | ||
2144 | methods.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 | |||
81 | import shutil | 72 | import shutil |
82 | import subprocess | 73 | import subprocess |
83 | import tempfile | 74 | import tempfile |
75 | import urllib | ||
84 | import bb | 76 | import bb |
85 | import bb.progress | 77 | import bb.progress |
86 | from contextlib import contextmanager | 78 | from 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 | """ | ||
2 | BitBake 'Fetch' implementation for Go modules | ||
3 | |||
4 | The gomod/gomodgit fetchers are used to download Go modules to the module cache | ||
5 | from a module proxy or directly from a version control repository. | ||
6 | |||
7 | Example SRC_URI: | ||
8 | |||
9 | SRC_URI += "gomod://golang.org/x/net;version=v0.9.0;sha256sum=..." | ||
10 | SRC_URI += "gomodgit://golang.org/x/net;version=v0.9.0;repo=go.googlesource.com/net;srcrev=..." | ||
11 | |||
12 | Required SRC_URI parameters: | ||
13 | |||
14 | - version | ||
15 | The version of the module. | ||
16 | |||
17 | Optional 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 | |||
48 | Related 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 | |||
58 | See the Go modules reference, https://go.dev/ref/mod, for more information | ||
59 | about the module cache, module proxies and version control systems. | ||
60 | """ | ||
61 | |||
62 | import hashlib | ||
63 | import os | ||
64 | import re | ||
65 | import shutil | ||
66 | import subprocess | ||
67 | import zipfile | ||
68 | |||
69 | import bb | ||
70 | from bb.fetch2 import FetchError | ||
71 | from bb.fetch2 import MissingParameterError | ||
72 | from bb.fetch2 import runfetchcmd | ||
73 | from bb.fetch2 import subprocess_setup | ||
74 | from bb.fetch2.git import Git | ||
75 | from bb.fetch2.wget import Wget | ||
76 | |||
77 | |||
78 | def 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 | |||
83 | class 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 | |||
162 | class 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 | ||
43 | def npm_package(package): | 43 | def 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 | ||
73 | class NpmShrinkWrap(FetchMethod): | 61 | class 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): | |||
53 | class Wget(FetchMethod): | 53 | class 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) |