2022-08-04 19:08:22 +02:00
|
|
|
# Copyright (c) 2022 Nordic Semiconductor ASA
|
|
|
|
#
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import os
|
|
|
|
from pathlib import Path
|
|
|
|
import sys
|
|
|
|
import textwrap
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
|
|
from west.commands import WestCommand
|
|
|
|
|
|
|
|
from zephyr_ext_common import ZEPHYR_BASE
|
|
|
|
|
|
|
|
sys.path.append(os.fspath(Path(__file__).parent.parent))
|
|
|
|
import zephyr_module
|
|
|
|
|
|
|
|
class Blobs(WestCommand):
|
|
|
|
|
2022-08-18 18:25:02 -07:00
|
|
|
DEFAULT_LIST_FMT = '{module} {status} {path} {type} {abspath}'
|
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
def __init__(self):
|
|
|
|
super().__init__(
|
|
|
|
'blobs',
|
|
|
|
# Keep this in sync with the string in west-commands.yml.
|
|
|
|
'work with binary blobs',
|
|
|
|
'Work with binary blobs',
|
|
|
|
accepts_unknown_args=False)
|
|
|
|
|
|
|
|
def do_add_parser(self, parser_adder):
|
|
|
|
parser = parser_adder.add_parser(
|
|
|
|
self.name,
|
|
|
|
help=self.help,
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
description=self.description,
|
|
|
|
epilog=textwrap.dedent(f'''\
|
|
|
|
FORMAT STRINGS
|
|
|
|
--------------
|
|
|
|
|
|
|
|
Blobs are listed using a Python 3 format string. Arguments
|
|
|
|
to the format string are accessed by name.
|
|
|
|
|
|
|
|
The default format string is:
|
|
|
|
|
2022-08-18 18:25:02 -07:00
|
|
|
"{self.DEFAULT_LIST_FMT}"
|
2022-08-04 19:08:22 +02:00
|
|
|
|
|
|
|
The following arguments are available:
|
|
|
|
|
|
|
|
- module: name of the module that contains this blob
|
|
|
|
- abspath: blob absolute path
|
|
|
|
- status: short status (A: present, M: hash failure, D: not present)
|
|
|
|
- path: blob local path from <module>/zephyr/blobs/
|
|
|
|
- sha256: blob SHA256 hash in hex
|
|
|
|
- type: type of blob
|
|
|
|
- version: version string
|
|
|
|
- license_path: path to the license file for the blob
|
2025-05-09 17:33:46 +08:00
|
|
|
- license-abspath: absolute path to the license file for the blob
|
|
|
|
- click-through: need license click-through or not
|
2022-08-04 19:08:22 +02:00
|
|
|
- uri: URI to the remote location of the blob
|
|
|
|
- description: blob text description
|
|
|
|
- doc-url: URL to the documentation for this blob
|
|
|
|
'''))
|
|
|
|
|
|
|
|
# Remember to update west-completion.bash if you add or remove
|
|
|
|
# flags
|
2022-08-17 13:48:13 +02:00
|
|
|
parser.add_argument('subcmd', nargs=1,
|
|
|
|
choices=['list', 'fetch', 'clean'],
|
2022-08-18 18:15:34 -07:00
|
|
|
help='sub-command to execute')
|
2022-08-04 19:08:22 +02:00
|
|
|
|
2022-08-16 19:02:43 +02:00
|
|
|
parser.add_argument('modules', metavar='MODULE', nargs='*',
|
2022-08-18 18:18:03 -07:00
|
|
|
help='''zephyr modules to operate on;
|
|
|
|
all modules will be used if not given''')
|
2022-08-04 19:08:22 +02:00
|
|
|
|
2022-08-18 18:25:02 -07:00
|
|
|
group = parser.add_argument_group('west blob list options')
|
|
|
|
group.add_argument('-f', '--format',
|
|
|
|
help='''format string to use to list each blob;
|
|
|
|
see FORMAT STRINGS below''')
|
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
return parser
|
|
|
|
|
|
|
|
def get_blobs(self, args):
|
|
|
|
blobs = []
|
|
|
|
modules = args.modules
|
2024-06-10 16:34:05 +02:00
|
|
|
all_modules = zephyr_module.parse_modules(ZEPHYR_BASE, self.manifest)
|
|
|
|
all_names = [m.meta.get('name', None) for m in all_modules]
|
|
|
|
|
|
|
|
unknown = set(modules) - set(all_names)
|
|
|
|
|
|
|
|
if len(unknown):
|
2024-09-29 15:28:14 +02:00
|
|
|
self.die(f'Unknown module(s): {unknown}')
|
2024-06-10 16:34:05 +02:00
|
|
|
|
|
|
|
for module in all_modules:
|
2022-08-04 19:08:22 +02:00
|
|
|
# Filter by module
|
|
|
|
module_name = module.meta.get('name', None)
|
2022-08-16 16:56:59 +02:00
|
|
|
if len(modules) and module_name not in modules:
|
2022-08-04 19:08:22 +02:00
|
|
|
continue
|
|
|
|
|
2022-08-24 15:42:55 +02:00
|
|
|
blobs += zephyr_module.process_blobs(module.project, module.meta)
|
2022-08-04 19:08:22 +02:00
|
|
|
|
|
|
|
return blobs
|
|
|
|
|
|
|
|
def list(self, args):
|
|
|
|
blobs = self.get_blobs(args)
|
2022-08-24 16:44:14 +02:00
|
|
|
fmt = args.format or self.DEFAULT_LIST_FMT
|
2022-08-04 19:08:22 +02:00
|
|
|
for blob in blobs:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.inf(fmt.format(**blob))
|
2022-08-04 19:08:22 +02:00
|
|
|
|
2022-08-17 15:06:17 +02:00
|
|
|
def ensure_folder(self, path):
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
def fetch_blob(self, url, path):
|
|
|
|
scheme = urlparse(url).scheme
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg(f'Fetching {path} with {scheme}')
|
2022-08-04 19:08:22 +02:00
|
|
|
import fetchers
|
|
|
|
fetcher = fetchers.get_fetcher_cls(scheme)
|
|
|
|
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg(f'Found fetcher: {fetcher}')
|
2022-08-04 19:08:22 +02:00
|
|
|
inst = fetcher()
|
2022-08-17 15:06:17 +02:00
|
|
|
self.ensure_folder(path)
|
2022-08-04 19:08:22 +02:00
|
|
|
inst.fetch(url, path)
|
|
|
|
|
2024-07-22 16:31:27 +02:00
|
|
|
# Compare the checksum of a file we've just downloaded
|
|
|
|
# to the digest in blob metadata, warn user if they differ.
|
2025-01-30 10:51:04 -05:00
|
|
|
def verify_blob(self, blob) -> bool:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg('Verifying blob {module}: {abspath}'.format(**blob))
|
2024-07-22 16:31:27 +02:00
|
|
|
|
|
|
|
status = zephyr_module.get_blob_status(blob['abspath'], blob['sha256'])
|
|
|
|
if status == zephyr_module.BLOB_OUTDATED:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.err(textwrap.dedent(
|
2024-07-22 16:31:27 +02:00
|
|
|
f'''\
|
|
|
|
The checksum of the downloaded file does not match that
|
|
|
|
in the blob metadata:
|
|
|
|
- if it is not certain that the download was successful,
|
|
|
|
try running 'west blobs fetch {blob['module']}'
|
|
|
|
to re-download the file
|
|
|
|
- if the error persists, please consider contacting
|
|
|
|
the maintainers of the module so that they can check
|
|
|
|
the corresponding blob metadata
|
|
|
|
|
|
|
|
Module: {blob['module']}
|
|
|
|
Blob: {blob['path']}
|
|
|
|
URL: {blob['url']}
|
|
|
|
Info: {blob['description']}'''))
|
2025-01-30 10:51:04 -05:00
|
|
|
return False
|
|
|
|
return True
|
2024-07-22 16:31:27 +02:00
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
def fetch(self, args):
|
2025-01-30 10:51:04 -05:00
|
|
|
bad_checksum_count = 0
|
2022-08-04 19:08:22 +02:00
|
|
|
blobs = self.get_blobs(args)
|
|
|
|
for blob in blobs:
|
2024-07-15 15:48:13 +02:00
|
|
|
if blob['status'] == zephyr_module.BLOB_PRESENT:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg('Blob {module}: {abspath} is up to date'.format(**blob))
|
2022-08-04 19:08:22 +02:00
|
|
|
continue
|
2024-09-29 15:28:14 +02:00
|
|
|
self.inf('Fetching blob {module}: {abspath}'.format(**blob))
|
2025-05-09 17:33:46 +08:00
|
|
|
|
|
|
|
if blob['click-through']:
|
|
|
|
while True:
|
|
|
|
user_input = input("For this blob, need to read and accept "
|
|
|
|
"license to continue. Read it?\n"
|
|
|
|
"Please type 'y' or 'n' and press enter to confirm: ")
|
|
|
|
if user_input.upper() == "Y" or user_input.upper() == "N":
|
|
|
|
break
|
|
|
|
|
|
|
|
if user_input.upper() != "Y":
|
|
|
|
self.wrn('Skip fetching this blob.')
|
|
|
|
continue
|
|
|
|
|
|
|
|
with open(blob['license-abspath']) as license_file:
|
|
|
|
license_content = license_file.read()
|
|
|
|
print(license_content)
|
|
|
|
|
|
|
|
while True:
|
|
|
|
user_input = input("Accept license to continue?\n"
|
|
|
|
"Please type 'y' or 'n' and press enter to confirm: ")
|
|
|
|
if user_input.upper() == "Y" or user_input.upper() == "N":
|
|
|
|
break
|
|
|
|
|
|
|
|
if user_input.upper() != "Y":
|
|
|
|
self.wrn('Skip fetching this blob.')
|
|
|
|
continue
|
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
self.fetch_blob(blob['url'], blob['abspath'])
|
2025-01-30 10:51:04 -05:00
|
|
|
if not self.verify_blob(blob):
|
|
|
|
bad_checksum_count += 1
|
|
|
|
|
|
|
|
if bad_checksum_count:
|
|
|
|
self.err(f"{bad_checksum_count} blobs have bad checksums")
|
|
|
|
sys.exit(os.EX_DATAERR)
|
2022-08-04 19:08:22 +02:00
|
|
|
|
2022-08-17 13:48:13 +02:00
|
|
|
def clean(self, args):
|
|
|
|
blobs = self.get_blobs(args)
|
|
|
|
for blob in blobs:
|
2024-07-15 15:48:13 +02:00
|
|
|
if blob['status'] == zephyr_module.BLOB_NOT_PRESENT:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg('Blob {module}: {abspath} not in filesystem'.format(**blob))
|
2022-08-17 13:48:13 +02:00
|
|
|
continue
|
2024-09-29 15:28:14 +02:00
|
|
|
self.inf('Deleting blob {module}: {status} {abspath}'.format(**blob))
|
2022-08-17 13:48:13 +02:00
|
|
|
blob['abspath'].unlink()
|
2022-08-04 19:08:22 +02:00
|
|
|
|
|
|
|
def do_run(self, args, _):
|
2024-09-29 15:28:14 +02:00
|
|
|
self.dbg(f'subcmd: \'{args.subcmd[0]}\' modules: {args.modules}')
|
2022-08-04 19:08:22 +02:00
|
|
|
|
|
|
|
subcmd = getattr(self, args.subcmd[0])
|
2022-08-18 18:25:02 -07:00
|
|
|
|
2022-08-24 15:51:44 +02:00
|
|
|
if args.subcmd[0] != 'list' and args.format is not None:
|
2024-09-29 15:28:14 +02:00
|
|
|
self.die(f'unexpected --format argument; this is a "west blobs list" option')
|
2022-08-18 18:25:02 -07:00
|
|
|
|
2022-08-04 19:08:22 +02:00
|
|
|
subcmd(args)
|