You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
10 KiB

4 years ago
#!/usr/bin/env python3
#
# This file is part of the MicroPython project, http://micropython.org/
#
# The MIT License (MIT)
#
# Copyright (c) 2019 Damien P. George
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import print_function
import sys
import os
import subprocess
###########################################################################
# Public functions to be used in the manifest
def include(manifest):
"""Include another manifest.
The manifest argument can be a string (filename) or an iterable of
strings.
Relative paths are resolved with respect to the current manifest file.
"""
if not isinstance(manifest, str):
for m in manifest:
include(m)
else:
manifest = convert_path(manifest)
with open(manifest) as f:
# Make paths relative to this manifest file while processing it.
# Applies to includes and input files.
prev_cwd = os.getcwd()
os.chdir(os.path.dirname(manifest))
exec(f.read())
os.chdir(prev_cwd)
def freeze(path, script=None, opt=0):
"""Freeze the input, automatically determining its type. A .py script
will be compiled to a .mpy first then frozen, and a .mpy file will be
frozen directly.
`path` must be a directory, which is the base directory to search for
files from. When importing the resulting frozen modules, the name of
the module will start after `path`, ie `path` is excluded from the
module name.
If `path` is relative, it is resolved to the current manifest.py.
Use $(MPY_DIR), $(MPY_LIB_DIR), $(PORT_DIR), $(BOARD_DIR) if you need
to access specific paths.
If `script` is None all files in `path` will be frozen.
If `script` is an iterable then freeze() is called on all items of the
iterable (with the same `path` and `opt` passed through).
If `script` is a string then it specifies the filename to freeze, and
can include extra directories before the file. The file will be
searched for in `path`.
`opt` is the optimisation level to pass to mpy-cross when compiling .py
to .mpy.
"""
freeze_internal(KIND_AUTO, path, script, opt)
def freeze_as_str(path):
"""Freeze the given `path` and all .py scripts within it as a string,
which will be compiled upon import.
"""
freeze_internal(KIND_AS_STR, path, None, 0)
def freeze_as_mpy(path, script=None, opt=0):
"""Freeze the input (see above) by first compiling the .py scripts to
.mpy files, then freezing the resulting .mpy files.
"""
freeze_internal(KIND_AS_MPY, path, script, opt)
def freeze_mpy(path, script=None, opt=0):
"""Freeze the input (see above), which must be .mpy files that are
frozen directly.
"""
freeze_internal(KIND_MPY, path, script, opt)
###########################################################################
# Internal implementation
KIND_AUTO = 0
KIND_AS_STR = 1
KIND_AS_MPY = 2
KIND_MPY = 3
VARS = {}
manifest_list = []
class FreezeError(Exception):
pass
def system(cmd):
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
return 0, output
except subprocess.CalledProcessError as er:
return -1, er.output
def convert_path(path):
# Perform variable substituion.
for name, value in VARS.items():
path = path.replace('$({})'.format(name), value)
# Convert to absolute path (so that future operations don't rely on
# still being chdir'ed).
return os.path.abspath(path)
def get_timestamp(path, default=None):
try:
stat = os.stat(path)
return stat.st_mtime
except OSError:
if default is None:
raise FreezeError('cannot stat {}'.format(path))
return default
def get_timestamp_newest(path):
ts_newest = 0
for dirpath, dirnames, filenames in os.walk(path, followlinks=True):
for f in filenames:
ts_newest = max(ts_newest, get_timestamp(os.path.join(dirpath, f)))
return ts_newest
def mkdir(path):
cur_path = ''
for p in path.split('/')[:-1]:
cur_path += p + '/'
try:
os.mkdir(cur_path)
except OSError as er:
if er.args[0] == 17: # file exists
pass
else:
raise er
def freeze_internal(kind, path, script, opt):
path = convert_path(path)
if script is None and kind == KIND_AS_STR:
if any(f[0] == KIND_AS_STR for f in manifest_list):
raise FreezeError('can only freeze one str directory')
manifest_list.append((KIND_AS_STR, path, script, opt))
elif script is None:
for dirpath, dirnames, filenames in os.walk(path, followlinks=True):
for f in filenames:
freeze_internal(kind, path, (dirpath + '/' + f)[len(path) + 1:], opt)
elif not isinstance(script, str):
for s in script:
freeze_internal(kind, path, s, opt)
else:
extension_kind = {KIND_AS_MPY: '.py', KIND_MPY: '.mpy'}
if kind == KIND_AUTO:
for k, ext in extension_kind.items():
if script.endswith(ext):
kind = k
break
else:
print('warn: unsupported file type, skipped freeze: {}'.format(script))
return
wanted_extension = extension_kind[kind]
if not script.endswith(wanted_extension):
raise FreezeError('expecting a {} file, got {}'.format(wanted_extension, script))
manifest_list.append((kind, path, script, opt))
def main():
# Parse arguments
import argparse
cmd_parser = argparse.ArgumentParser(description='A tool to generate frozen content in MicroPython firmware images.')
cmd_parser.add_argument('-o', '--output', help='output path')
cmd_parser.add_argument('-b', '--build-dir', help='output path')
cmd_parser.add_argument('-f', '--mpy-cross-flags', default='', help='flags to pass to mpy-cross')
cmd_parser.add_argument('-v', '--var', action='append', help='variables to substitute')
cmd_parser.add_argument('files', nargs='+', help='input manifest list')
args = cmd_parser.parse_args()
# Extract variables for substitution.
for var in args.var:
name, value = var.split('=', 1)
if os.path.exists(value):
value = os.path.abspath(value)
VARS[name] = value
if 'MPY_DIR' not in VARS or 'PORT_DIR' not in VARS:
print('MPY_DIR and PORT_DIR variables must be specified')
sys.exit(1)
# Get paths to tools
MAKE_FROZEN = VARS['MPY_DIR'] + '/tools/make-frozen.py'
Dev v1.0.7 (#57) * PASS1-133: Modify cosign file naming for fully signed binaries. (#34) * PASS1-133: Modify cosign file naming for fully signed binaries. * Remove all USE_CRYPTO, it is not used anymore Also remove function that is not needed. * Added use of firmware version from header, also fixed a seg fault * PASS1-135: Fix sticky up or down key (#33) * PASS1-135: Fix sticky up or down key * Add comment for input.reset function * PASS1-128: Add support back for Bitcoin testnet (#29) * Fixes part of PASS-91 Show address and index of the address being verified * Second half of fix for ENV1-91 Add better messaging for address range searching Fix a bug when saving next_addrs (was comparing dicts by ref) * Fixes PASS1-122 Check change addresses in addition to receive address in "Verify Address" * Fix comment punctuation * Show backup filename to user after successful backup (#18) Fix PASS1-92 * Auto-truncate multisig config names (#19) Fix PASS1-101 * PASS1-101: Auto-truncate multisig config names (#19) Fix PASS1-101 * Remove unnecessary comments * PASS1-92 (#20) * Show backup filename to user after successful backup Fix PASS1-92 * Add missing 'card' parameter to `get_backups_folder_path()` calls * Revert path function changes since 'card' is not available * PASS1-102: Fix backwards microSD issue Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure. Updated `HAL_SD_Init()` to reset it before attempting initialization. * PASS1-102: Fix backwards microSD issue (#21) Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure. Updated `HAL_SD_Init()` to reset it before attempting initialization. * PASS1-102_b (#22) * PASS1-102: Fix backwards microSD issue Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure. Updated `HAL_SD_Init()` to reset it before attempting initialization. * Switch back to hard-coded path for now * PASS1-122_b (#23) * PASS1-102: Fix backwards microSD issue Found that `ErrorCode` in `SD_HandleTypeDef` was not reset after a failure. Updated `HAL_SD_Init()` to reset it before attempting initialization. * Update user messaging for found/not found case of Verify Address Fix bug with trailing space at end of line in `word_wrap()` * Strip ever time through the loop * PASS1-125: Add Git commit-msg hook to check for Linear ID (#24) * PASS1-125: Add Git commit-msg hook to check for Linear ID * Update .githooks/commit-msg Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com> Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com> * PASS1-122: Minor updates to text (#27) * PASS1-127: Fix `reuse lint` issues in the repo (#26) * PASS1-113: Give the user a way to clear the developer pubkey slot (#25) * PASS1-122: Added "Address Verified" text to new wallet pairing (#28) * PASS1-122: Minor updates to text * PASS1-122: Added "Address Verified" text to new wallet pairing * PASS1-128: Add support back for Bitcoin testnet Co-authored-by: Ken Carpenter <ken@foundationdevices.com> Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com> Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com> * PASS1-56: Use XFP in backups filename and don't save `backup_num` (#32) * PASS1-34: Refactor find address code so there is only one copy (#37) * PASS1-94: Prevent installing user-signed firmware if no user-key installed (#38) * PASS1-94: Prevent installing user-signed firmware if no user signing key installed * Fixed case where user pubkey was removed manually * Fixed text to match other areas where text is used * Update text message for developer pubkey * Hard coded user signed field to false Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com> * PASS1-55: Add menu to switch to a different Passphrase without rebooting (#35) * PASS1-55: Add menu to switch to a different Passphrase without rebooting * Changed order of menu items in Passphrase menu * Modified menu titles and removed "a" from inconsistent text * PASS1-137: Add Justfile support to Gen 1 repo (#36) * PASS1-137: Add Justfile support to Gen 1 repo First pass - not all expected commands are added yet * Update Justfile with fmt command Add py and c/h formatting Need to finalize .clang-format file before doing a full reformatting PR * Refactor Justfiles to separate them out Also add graphics build commands * Update Justfiles a bit Fix formatting of graphics header files in preparation for automatic code formatting * PASS1-139: Implement code to allow OCD to capture a screenshot over JTAG (#42) * PASS1-139: Implement code to allow OCD to capture a screenshot over JTAG * Update sram4.py * PASS1-132: Remove duplicate file compilation (#39) * PASS1-78: In display.text_input, split lines based on pixel widths (#41) * PASS1-78: In display.text_input, split lines based on pixel widths * Check for StringIO object before calling split_by_char_size * PASS1-89: Show exported filename when exporting wallet to microSD (#43) * PASS1-89: Show exported filename when exporting wallet to microSD * Deleted/commented unnecessary lines * PASS1-136: Add Specter wallet back once they fix UR issues (#44) * PASS1-136: Add Specter wallet back once they fix UR issues * Rebase onto dev-v1.0.7 * Remove passport from export filename * Remove flag from all wallets besides Specter wallet * Removed flag from unnecessary field and renamed flag to import * Renamed multisig_import function * PASS1-112: Passphrase input dialog improvements (#48) * PASS1-112: Passphrase input dialog improvements The passphrase is limited to 64 characters. The line spacing was reduced to make room for 7 lines. 63 capital W's will fill all 7 lines (+1 over), otherwise 64 characters usually takes about 4 lines. * Add constant for max message length * TOOL-3: Setup Docker infra for Gen 1 Development (#45) * Add Dockerfile for building the firmware Setting up a local environment for building the firmware can be a painful process. This wraps that process up in a Dockerfile containing all the deps needed which is then used in the justfile to build the firmware. * Add just targets for signing and cleaning * Change sha target to take a sha and verify it directly * Add docs for verifying the firmware SHA sum * Add version param to sign just target * Update verify-sha output to be more explicit * PASS1-67: Change unit to sats in settings (#46) * PASS1-67: Change unit to sats in settings * Added warnings for Testnet and made the setting volatile * Added 'chain' removal to schema_evolution and moved Units menu to top * Moved Units below Change Pin in menu * TOOL-4: Implement CI for Passport Gen 1 build (#49) * TOOL-4: Create CI for firmware build * TOOL-4: Improve handling of git describe output * TOOL-4: Rename Justfile to match others in repo * TOOL-4: Add caching and separated Docker building in CI * TOOL-4: Update CI to push image to local registry service * TOOL-4: Update CI to allow customizing of D_BASE * TOOL-4: Change clang format action * TOOL-4: User correct clang format version * TOOL-4: YAML :( * TOOL-4: Update to clang-format-10.0 * TOOL-4: Updaet to 10 * TOOL-4: Build and export the bootloader * TOOL-4: Add D_BASE to bootload build step * TOOL-4: Correctly pass D_BASE to bootloader job * TOOL-4: Update bootloader make path in Justfile * TOOL-4: Update CI to output tools * PASS1-140: Add Justfile commands to DEVELOPMENT.md (#51) * PASS1-140: Add Justfile commands to DEVELOPMENT.md * Update DEVELOPMENT.md * Update DEVELOPMENT.md Co-authored-by: Ken Carpenter <62639971+FoundationKen@users.noreply.github.com> * PASS1-148: Fix missing address prefixes for testnet (#53) * PASS1-148: Fix missing address prefixes for testnet * Add comma separations to sats values * Casa support added * Added testnet prefix check to Verify Address process * PASS1-150: Fixed missing argument in `import_from_psbt()` call (#55) * PASS1-150: Fixed missing argument in `import_from_psbt()` call Also fixed typo in function description. * Added a space between value and label of BTC/sats * Disable Casa Support Casa has not approved the support for Passport yet, until then Casa is disabled temporarily. Co-authored-by: Corey Lakey <corey.lakey@gmail.com> Co-authored-by: Jean Pierre Dudey <jeandudey@hotmail.com> Co-authored-by: Alex Sears <searsaw@users.noreply.github.com>
3 years ago
MPY_CROSS = VARS['MPY_CROSS']
4 years ago
MPY_TOOL = VARS['MPY_DIR'] + '/tools/mpy-tool.py'
# Ensure mpy-cross is built
if not os.path.exists(MPY_CROSS):
print('mpy-cross not found at {}, please build it first'.format(MPY_CROSS))
sys.exit(1)
# Include top-level inputs, to generate the manifest
for input_manifest in args.files:
try:
if input_manifest.endswith('.py'):
include(input_manifest)
else:
exec(input_manifest)
except FreezeError as er:
print('freeze error executing "{}": {}'.format(input_manifest, er.args[0]))
sys.exit(1)
# Process the manifest
str_paths = []
mpy_files = []
ts_newest = 0
for kind, path, script, opt in manifest_list:
if kind == KIND_AS_STR:
str_paths.append(path)
ts_outfile = get_timestamp_newest(path)
elif kind == KIND_AS_MPY:
infile = '{}/{}'.format(path, script)
outfile = '{}/frozen_mpy/{}.mpy'.format(args.build_dir, script[:-3])
ts_infile = get_timestamp(infile)
ts_outfile = get_timestamp(outfile, 0)
if ts_infile >= ts_outfile:
print('MPY', script)
mkdir(outfile)
res, out = system([MPY_CROSS] + args.mpy_cross_flags.split() + ['-o', outfile, '-s', script, '-O{}'.format(opt), infile])
if res != 0:
print('error compiling {}: {}'.format(infile, out))
raise SystemExit(1)
ts_outfile = get_timestamp(outfile)
mpy_files.append(outfile)
else:
assert kind == KIND_MPY
infile = '{}/{}'.format(path, script)
mpy_files.append(infile)
ts_outfile = get_timestamp(infile)
ts_newest = max(ts_newest, ts_outfile)
# Check if output file needs generating
if ts_newest < get_timestamp(args.output, 0):
# No files are newer than output file so it does not need updating
return
# Freeze paths as strings
res, output_str = system([sys.executable, MAKE_FROZEN] + str_paths)
if res != 0:
print('error freezing strings {}: {}'.format(str_paths, output_str))
sys.exit(1)
# Freeze .mpy files
res, output_mpy = system([sys.executable, MPY_TOOL, '-f', '-q', args.build_dir + '/genhdr/qstrdefs.preprocessed.h'] + mpy_files)
if res != 0:
print('error freezing mpy {}: {}'.format(mpy_files, output_mpy))
sys.exit(1)
# Generate output
print('GEN', args.output)
mkdir(args.output)
with open(args.output, 'wb') as f:
f.write(b'//\n// Content for MICROPY_MODULE_FROZEN_STR\n//\n')
f.write(output_str)
f.write(b'//\n// Content for MICROPY_MODULE_FROZEN_MPY\n//\n')
f.write(output_mpy)
if __name__ == '__main__':
main()