erase unused flash pages so hashes matches (#392)
* erase unused flash pages so hashes matches * always pad the kernel, adds docstrings * added type hints * fix typos
This commit is contained in:
150
deploy.py
150
deploy.py
@@ -29,6 +29,7 @@ import shutil
|
|||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
from six.moves import input
|
from six.moves import input
|
||||||
@@ -159,7 +160,8 @@ SUPPORTED_BOARDS = {
|
|||||||
APP_HEAP_SIZE = 90000
|
APP_HEAP_SIZE = 90000
|
||||||
|
|
||||||
|
|
||||||
def get_supported_boards():
|
def get_supported_boards() -> Tuple[str]:
|
||||||
|
"""Returns a tuple all valid supported boards."""
|
||||||
boards = []
|
boards = []
|
||||||
for name, props in SUPPORTED_BOARDS.items():
|
for name, props in SUPPORTED_BOARDS.items():
|
||||||
if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
|
if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
|
||||||
@@ -168,28 +170,28 @@ def get_supported_boards():
|
|||||||
return tuple(set(boards))
|
return tuple(set(boards))
|
||||||
|
|
||||||
|
|
||||||
def fatal(msg):
|
def fatal(msg: str):
|
||||||
print(f"{colorama.Fore.RED + colorama.Style.BRIGHT}fatal:"
|
print(f"{colorama.Fore.RED + colorama.Style.BRIGHT}fatal:"
|
||||||
f"{colorama.Style.RESET_ALL} {msg}")
|
f"{colorama.Style.RESET_ALL} {msg}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def error(msg):
|
def error(msg: str):
|
||||||
print(f"{colorama.Fore.RED}error:{colorama.Style.RESET_ALL} {msg}")
|
print(f"{colorama.Fore.RED}error:{colorama.Style.RESET_ALL} {msg}")
|
||||||
|
|
||||||
|
|
||||||
def info(msg):
|
def info(msg: str):
|
||||||
print(f"{colorama.Fore.GREEN + colorama.Style.BRIGHT}info:"
|
print(f"{colorama.Fore.GREEN + colorama.Style.BRIGHT}info:"
|
||||||
f"{colorama.Style.RESET_ALL} {msg}")
|
f"{colorama.Style.RESET_ALL} {msg}")
|
||||||
|
|
||||||
|
|
||||||
def assert_mandatory_binary(binary):
|
def assert_mandatory_binary(binary_name: str):
|
||||||
if not shutil.which(binary):
|
if not shutil.which(binary_name):
|
||||||
fatal((f"Couldn't find {binary} binary. Make sure it is installed and "
|
fatal((f"Couldn't find {binary_name} binary. Make sure it is installed and "
|
||||||
"that your PATH is set correctly."))
|
"that your PATH is set correctly."))
|
||||||
|
|
||||||
|
|
||||||
def assert_python_library(module):
|
def assert_python_library(module: str):
|
||||||
try:
|
try:
|
||||||
__import__(module)
|
__import__(module)
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
@@ -197,7 +199,20 @@ def assert_python_library(module):
|
|||||||
f"Try to run: pip3 install {module}"))
|
f"Try to run: pip3 install {module}"))
|
||||||
|
|
||||||
|
|
||||||
def create_metadata(firmware_image, partition_address):
|
def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
|
||||||
|
"""Creates the matching metadata for the given firmware.
|
||||||
|
|
||||||
|
The metadata consists of a timestamp, the expected address and a hash of
|
||||||
|
the image and the other properties in this metadata.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
firmware_image: A byte array of kernel and app, padding to full length.
|
||||||
|
partition_address: The address to be written as a metadata property.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A byte array consisting of 32B hash, 4B timestamp and 4B partition address
|
||||||
|
in little endian encoding.
|
||||||
|
"""
|
||||||
t = datetime.datetime.utcnow().timestamp()
|
t = datetime.datetime.utcnow().timestamp()
|
||||||
timestamp = struct.pack("<I", int(t))
|
timestamp = struct.pack("<I", int(t))
|
||||||
partition_start = struct.pack("<I", partition_address)
|
partition_start = struct.pack("<I", partition_address)
|
||||||
@@ -209,6 +224,14 @@ def create_metadata(firmware_image, partition_address):
|
|||||||
return checksum + timestamp + partition_start
|
return checksum + timestamp + partition_start
|
||||||
|
|
||||||
|
|
||||||
|
def pad_to(binary: bytes, length: int) -> bytes:
|
||||||
|
"""Extends the given binary to the new length with a 0xFF padding."""
|
||||||
|
if len(binary) > length:
|
||||||
|
fatal(f"Binary size {len(binary)} exceeds flash partition {length}.")
|
||||||
|
padding = bytes([0xFF] * (length - len(binary)))
|
||||||
|
return binary + padding
|
||||||
|
|
||||||
|
|
||||||
class RemoveConstAction(argparse.Action):
|
class RemoveConstAction(argparse.Action):
|
||||||
|
|
||||||
# pylint: disable=redefined-builtin
|
# pylint: disable=redefined-builtin
|
||||||
@@ -247,6 +270,18 @@ class RemoveConstAction(argparse.Action):
|
|||||||
|
|
||||||
|
|
||||||
class OpenSKInstaller:
|
class OpenSKInstaller:
|
||||||
|
"""Checks, builds and installs various parts of OpenSK.
|
||||||
|
|
||||||
|
This module can perform the following tasks:
|
||||||
|
- build and install Tock OS
|
||||||
|
- check, build and install the main ctap2 application
|
||||||
|
- build and install example applications
|
||||||
|
- write padding
|
||||||
|
- erase apps and persistent storage
|
||||||
|
- write metadata entries for upgradable boards
|
||||||
|
|
||||||
|
OpenSKInstaller(args).run()
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
@@ -276,7 +311,21 @@ class OpenSKInstaller:
|
|||||||
port=None,
|
port=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def checked_command(self, cmd, env=None, cwd=None):
|
def checked_command(self,
|
||||||
|
cmd: List[str],
|
||||||
|
env: Dict[str, str] = None,
|
||||||
|
cwd: str = None):
|
||||||
|
"""Executes the given command.
|
||||||
|
|
||||||
|
Outside of debug mode, the command's output is muted. Exits if the called
|
||||||
|
process returns an error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cmd: A list of strings. The first string is the command, the other list
|
||||||
|
elements are parameters to that command."
|
||||||
|
env: The dictionary of environment variables.
|
||||||
|
cwd: The directory to execute from.
|
||||||
|
"""
|
||||||
stdout = None if self.args.verbose_build else subprocess.DEVNULL
|
stdout = None if self.args.verbose_build else subprocess.DEVNULL
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
@@ -284,7 +333,11 @@ class OpenSKInstaller:
|
|||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
fatal(f"Failed to execute {cmd[0]}: {str(e)}")
|
fatal(f"Failed to execute {cmd[0]}: {str(e)}")
|
||||||
|
|
||||||
def checked_command_output(self, cmd, env=None, cwd=None):
|
def checked_command_output(self,
|
||||||
|
cmd: List[str],
|
||||||
|
env: Dict[str, str] = None,
|
||||||
|
cwd: str = None) -> str:
|
||||||
|
"""Executes cmd like checked_command, but returns the output."""
|
||||||
cmd_output = ""
|
cmd_output = ""
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.run(
|
cmd_output = subprocess.run(
|
||||||
@@ -300,6 +353,7 @@ class OpenSKInstaller:
|
|||||||
return cmd_output.decode()
|
return cmd_output.decode()
|
||||||
|
|
||||||
def update_rustc_if_needed(self):
|
def update_rustc_if_needed(self):
|
||||||
|
"""Updates the Rust and installs the necessary target toolchain."""
|
||||||
target_toolchain_fullstring = "stable"
|
target_toolchain_fullstring = "stable"
|
||||||
with open("rust-toolchain", "r", encoding="utf-8") as f:
|
with open("rust-toolchain", "r", encoding="utf-8") as f:
|
||||||
content = f.readlines()
|
content = f.readlines()
|
||||||
@@ -339,6 +393,7 @@ class OpenSKInstaller:
|
|||||||
info("Rust toolchain up-to-date")
|
info("Rust toolchain up-to-date")
|
||||||
|
|
||||||
def build_tockos(self):
|
def build_tockos(self):
|
||||||
|
"""Buids Tock OS with the parameters specified in args."""
|
||||||
info(f"Building Tock OS for board {self.args.board}")
|
info(f"Building Tock OS for board {self.args.board}")
|
||||||
props = SUPPORTED_BOARDS[self.args.board]
|
props = SUPPORTED_BOARDS[self.args.board]
|
||||||
out_directory = os.path.join("third_party", "tock", "target", props.arch,
|
out_directory = os.path.join("third_party", "tock", "target", props.arch,
|
||||||
@@ -351,15 +406,28 @@ class OpenSKInstaller:
|
|||||||
self.checked_command(["make"], cwd=props.path, env=env)
|
self.checked_command(["make"], cwd=props.path, env=env)
|
||||||
|
|
||||||
def build_example(self):
|
def build_example(self):
|
||||||
|
"""Builds an example with the name from args."""
|
||||||
info(f"Building example {self.args.application}")
|
info(f"Building example {self.args.application}")
|
||||||
self._build_app_or_example(is_example=True)
|
self._build_app_or_example(is_example=True)
|
||||||
|
|
||||||
def build_opensk(self):
|
def build_opensk(self):
|
||||||
|
"""Runs essential tests in OpenSK, then builds it if successful."""
|
||||||
info("Building OpenSK application")
|
info("Building OpenSK application")
|
||||||
self._check_invariants()
|
self._check_invariants()
|
||||||
self._build_app_or_example(is_example=False)
|
self._build_app_or_example(is_example=False)
|
||||||
|
|
||||||
def _build_app_or_example(self, is_example):
|
def _build_app_or_example(self, is_example: bool):
|
||||||
|
"""Builds the application specified through args.
|
||||||
|
|
||||||
|
This function specifies the used compile time flags, specifying the linker
|
||||||
|
script and reducing the binary size. It compiles the application and calls
|
||||||
|
elf2tab to create a TAB file out of the produced binary.
|
||||||
|
|
||||||
|
The settings in self.args have to match is_example.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
is_example: Whether args.application is an example or the main ctap2 app.
|
||||||
|
"""
|
||||||
assert self.args.application
|
assert self.args.application
|
||||||
# Ideally we would build a TAB file for all boards at once but depending on
|
# Ideally we would build a TAB file for all boards at once but depending on
|
||||||
# the chip on the board, the link script could be totally different.
|
# the chip on the board, the link script could be totally different.
|
||||||
@@ -400,11 +468,13 @@ class OpenSKInstaller:
|
|||||||
self.create_tab_file({props.arch: app_path})
|
self.create_tab_file({props.arch: app_path})
|
||||||
|
|
||||||
def _check_invariants(self):
|
def _check_invariants(self):
|
||||||
|
"""Runs selected unit tests to check preconditions in the code."""
|
||||||
print("Testing invariants in customization.rs...")
|
print("Testing invariants in customization.rs...")
|
||||||
self.checked_command_output(
|
self.checked_command_output(
|
||||||
["cargo", "test", "--features=std", "--lib", "customization"])
|
["cargo", "test", "--features=std", "--lib", "customization"])
|
||||||
|
|
||||||
def generate_crypto_materials(self, force_regenerate):
|
def generate_crypto_materials(self, force_regenerate: bool):
|
||||||
|
"""Calls a shell script that generates cryptographic material."""
|
||||||
has_error = subprocess.call([
|
has_error = subprocess.call([
|
||||||
os.path.join("tools", "gen_key_materials.sh"),
|
os.path.join("tools", "gen_key_materials.sh"),
|
||||||
"Y" if force_regenerate else "N",
|
"Y" if force_regenerate else "N",
|
||||||
@@ -413,8 +483,9 @@ class OpenSKInstaller:
|
|||||||
error(("Something went wrong while trying to generate ECC "
|
error(("Something went wrong while trying to generate ECC "
|
||||||
"key and/or certificate for OpenSK"))
|
"key and/or certificate for OpenSK"))
|
||||||
|
|
||||||
def create_tab_file(self, binaries):
|
def create_tab_file(self, binary_names: Dict[str, str]):
|
||||||
assert binaries
|
"""Checks and uses elf2tab to generated an TAB file out of the binaries."""
|
||||||
|
assert binary_names
|
||||||
assert self.args.application
|
assert self.args.application
|
||||||
info("Generating Tock TAB file for application/example "
|
info("Generating Tock TAB file for application/example "
|
||||||
f"{self.args.application}")
|
f"{self.args.application}")
|
||||||
@@ -433,7 +504,7 @@ class OpenSKInstaller:
|
|||||||
if self.args.verbose_build:
|
if self.args.verbose_build:
|
||||||
elf2tab_args.append("--verbose")
|
elf2tab_args.append("--verbose")
|
||||||
stack_sizes = set()
|
stack_sizes = set()
|
||||||
for arch, app_file in binaries.items():
|
for arch, app_file in binary_names.items():
|
||||||
dest_file = os.path.join(self.tab_folder, f"{arch}.elf")
|
dest_file = os.path.join(self.tab_folder, f"{arch}.elf")
|
||||||
shutil.copyfile(app_file, dest_file)
|
shutil.copyfile(app_file, dest_file)
|
||||||
elf2tab_args.append(dest_file)
|
elf2tab_args.append(dest_file)
|
||||||
@@ -457,7 +528,8 @@ class OpenSKInstaller:
|
|||||||
else:
|
else:
|
||||||
self.checked_command(elf2tab_args)
|
self.checked_command(elf2tab_args)
|
||||||
|
|
||||||
def install_tab_file(self, tab_filename):
|
def install_tab_file(self, tab_filename: str):
|
||||||
|
"""Calls Tockloader to install a TAB file."""
|
||||||
assert self.args.application
|
assert self.args.application
|
||||||
info(f"Installing Tock application {self.args.application}")
|
info(f"Installing Tock application {self.args.application}")
|
||||||
args = copy.copy(self.tockloader_default_args)
|
args = copy.copy(self.tockloader_default_args)
|
||||||
@@ -473,13 +545,15 @@ class OpenSKInstaller:
|
|||||||
fatal("Couldn't install Tock application "
|
fatal("Couldn't install Tock application "
|
||||||
f"{self.args.application}: {str(e)}")
|
f"{self.args.application}: {str(e)}")
|
||||||
|
|
||||||
def get_padding(self):
|
def get_padding(self) -> bytes:
|
||||||
|
"""Creates a padding application binary."""
|
||||||
padding = tbfh.TBFHeaderPadding(
|
padding = tbfh.TBFHeaderPadding(
|
||||||
SUPPORTED_BOARDS[self.args.board].app_address -
|
SUPPORTED_BOARDS[self.args.board].app_address -
|
||||||
SUPPORTED_BOARDS[self.args.board].padding_address)
|
SUPPORTED_BOARDS[self.args.board].padding_address)
|
||||||
return padding.get_binary()
|
return padding.get_binary()
|
||||||
|
|
||||||
def write_binary(self, binary, address):
|
def write_binary(self, binary: bytes, address: int):
|
||||||
|
"""Writes a binary to the device's flash at the given address."""
|
||||||
tock = loader.TockLoader(self.tockloader_default_args)
|
tock = loader.TockLoader(self.tockloader_default_args)
|
||||||
tock.open()
|
tock.open()
|
||||||
try:
|
try:
|
||||||
@@ -487,7 +561,8 @@ class OpenSKInstaller:
|
|||||||
except TockLoaderException as e:
|
except TockLoaderException as e:
|
||||||
fatal(f"Couldn't write binary: {str(e)}")
|
fatal(f"Couldn't write binary: {str(e)}")
|
||||||
|
|
||||||
def read_kernel(self):
|
def read_kernel(self) -> bytes:
|
||||||
|
"""Reads the kernel file from disk and returns it as a byte array."""
|
||||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||||
kernel_file = os.path.join("third_party", "tock", "target",
|
kernel_file = os.path.join("third_party", "tock", "target",
|
||||||
board_props.arch, "release",
|
board_props.arch, "release",
|
||||||
@@ -496,14 +571,23 @@ class OpenSKInstaller:
|
|||||||
fatal(f"File not found: {kernel_file}")
|
fatal(f"File not found: {kernel_file}")
|
||||||
with open(kernel_file, "rb") as firmware:
|
with open(kernel_file, "rb") as firmware:
|
||||||
kernel = firmware.read()
|
kernel = firmware.read()
|
||||||
return kernel
|
|
||||||
|
# Pads the kernel to the expected length.
|
||||||
|
if board_props.padding_address is None:
|
||||||
|
end_address = board_props.app_address
|
||||||
|
else:
|
||||||
|
end_address = board_props.padding_address
|
||||||
|
kernel_size = end_address - board_props.kernel_address
|
||||||
|
return pad_to(kernel, kernel_size)
|
||||||
|
|
||||||
def install_tock_os(self):
|
def install_tock_os(self):
|
||||||
|
"""Reads the kernel from disk and writes it to the device's flash."""
|
||||||
kernel = self.read_kernel()
|
kernel = self.read_kernel()
|
||||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||||
self.write_binary(kernel, board_props.kernel_address)
|
self.write_binary(kernel, board_props.kernel_address)
|
||||||
|
|
||||||
def install_padding(self):
|
def install_padding(self):
|
||||||
|
"""Generates a padding application and writes it to the address in args."""
|
||||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||||
if board_props.padding_address is None:
|
if board_props.padding_address is None:
|
||||||
return
|
return
|
||||||
@@ -511,13 +595,7 @@ class OpenSKInstaller:
|
|||||||
self.write_binary(self.get_padding(), board_props.padding_address)
|
self.write_binary(self.get_padding(), board_props.padding_address)
|
||||||
|
|
||||||
def install_metadata(self):
|
def install_metadata(self):
|
||||||
|
"""Generates and writes firmware metadata at the metadata address."""
|
||||||
def pad_to(binary, length):
|
|
||||||
if len(binary) > length:
|
|
||||||
fatal(f"Binary size {len(binary)} exceeds flash partition {length}.")
|
|
||||||
padding = bytes([0xFF] * (length - len(binary)))
|
|
||||||
return binary + padding
|
|
||||||
|
|
||||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||||
if board_props.metadata_address is None:
|
if board_props.metadata_address is None:
|
||||||
return
|
return
|
||||||
@@ -534,7 +612,8 @@ class OpenSKInstaller:
|
|||||||
|
|
||||||
kernel_size = board_props.app_address - board_props.kernel_address
|
kernel_size = board_props.app_address - board_props.kernel_address
|
||||||
app_size = board_props.firmware_size - kernel_size
|
app_size = board_props.firmware_size - kernel_size
|
||||||
firmware_image = pad_to(kernel, kernel_size) + pad_to(app, app_size)
|
# The kernel is already padded when read.
|
||||||
|
firmware_image = kernel + pad_to(app, app_size)
|
||||||
|
|
||||||
metadata = create_metadata(firmware_image, board_props.kernel_address)
|
metadata = create_metadata(firmware_image, board_props.kernel_address)
|
||||||
if self.args.verbose_build:
|
if self.args.verbose_build:
|
||||||
@@ -544,6 +623,7 @@ class OpenSKInstaller:
|
|||||||
self.write_binary(metadata, board_props.metadata_address)
|
self.write_binary(metadata, board_props.metadata_address)
|
||||||
|
|
||||||
def clear_apps(self):
|
def clear_apps(self):
|
||||||
|
"""Uses Tockloader to erase all applications on the device."""
|
||||||
args = copy.copy(self.tockloader_default_args)
|
args = copy.copy(self.tockloader_default_args)
|
||||||
# Ensure we don't force erase all apps but only the apps starting
|
# Ensure we don't force erase all apps but only the apps starting
|
||||||
# at `board.app_address`. This makes sure we don't erase the padding.
|
# at `board.app_address`. This makes sure we don't erase the padding.
|
||||||
@@ -558,6 +638,7 @@ class OpenSKInstaller:
|
|||||||
info(f"A non-critical error occurred while erasing apps: {str(e)}")
|
info(f"A non-critical error occurred while erasing apps: {str(e)}")
|
||||||
|
|
||||||
def clear_storage(self):
|
def clear_storage(self):
|
||||||
|
"""Overwrites the storage's flash with 0xFF bytes."""
|
||||||
if self.args.programmer == "none":
|
if self.args.programmer == "none":
|
||||||
return 0
|
return 0
|
||||||
info("Erasing the persistent storage")
|
info("Erasing the persistent storage")
|
||||||
@@ -576,7 +657,8 @@ class OpenSKInstaller:
|
|||||||
fatal(f"Programmer {self.args.programmer} is not supported.")
|
fatal(f"Programmer {self.args.programmer} is not supported.")
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
def verify_flashed_app(self, expected_app):
|
def verify_flashed_app(self, expected_app: str) -> bool:
|
||||||
|
"""Uses Tockloader to check if an app of the expected name was written."""
|
||||||
if self.args.programmer not in ("jlink", "openocd"):
|
if self.args.programmer not in ("jlink", "openocd"):
|
||||||
return False
|
return False
|
||||||
tock = loader.TockLoader(self.tockloader_default_args)
|
tock = loader.TockLoader(self.tockloader_default_args)
|
||||||
@@ -587,7 +669,8 @@ class OpenSKInstaller:
|
|||||||
app_found = expected_app in apps
|
app_found = expected_app in apps
|
||||||
return app_found
|
return app_found
|
||||||
|
|
||||||
def create_hex_file(self, dest_file):
|
def create_hex_file(self, dest_file: str):
|
||||||
|
"""Creates an intelhex file from the kernel and app binaries on disk."""
|
||||||
# We produce an intelhex file with everything in it
|
# We produce an intelhex file with everything in it
|
||||||
# https://en.wikipedia.org/wiki/Intel_HEX
|
# https://en.wikipedia.org/wiki/Intel_HEX
|
||||||
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
||||||
@@ -626,6 +709,7 @@ class OpenSKInstaller:
|
|||||||
final_hex.tofile(dest_file, format="hex")
|
final_hex.tofile(dest_file, format="hex")
|
||||||
|
|
||||||
def check_prerequisites(self):
|
def check_prerequisites(self):
|
||||||
|
"""Checks versions of the used tools, exits on version mismatch."""
|
||||||
if not tockloader.__version__.startswith("1.5."):
|
if not tockloader.__version__.startswith("1.5."):
|
||||||
fatal(("Your version of tockloader seems incompatible: found "
|
fatal(("Your version of tockloader seems incompatible: found "
|
||||||
f"{tockloader.__version__}, expected 1.5.x."))
|
f"{tockloader.__version__}, expected 1.5.x."))
|
||||||
@@ -656,7 +740,8 @@ class OpenSKInstaller:
|
|||||||
if self.args.programmer == "none":
|
if self.args.programmer == "none":
|
||||||
assert_python_library("intelhex")
|
assert_python_library("intelhex")
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> int:
|
||||||
|
"""Reads args to decide and run all required tasks."""
|
||||||
self.check_prerequisites()
|
self.check_prerequisites()
|
||||||
self.update_rustc_if_needed()
|
self.update_rustc_if_needed()
|
||||||
|
|
||||||
@@ -770,6 +855,7 @@ class OpenSKInstaller:
|
|||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
"""Verifies some args, then runs a new OpenSKInstaller."""
|
||||||
colorama.init()
|
colorama.init()
|
||||||
|
|
||||||
# Make sure the current working directory is the right one before running
|
# Make sure the current working directory is the right one before running
|
||||||
|
|||||||
Reference in New Issue
Block a user