1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-27 12:39:04 +00:00

fix(core): remove debug-related data from PYOPT=1 firmware builds

It reduces T3T1 release universal firmware size by ~2kB:
```
Memory region         Used Size  Region Size  %age Used
           FLASH:     1580304 B      1664 KB     92.74%
```

Before this PR:
```
Memory region         Used Size  Region Size  %age Used
           FLASH:     1578256 B      1664 KB     92.62%
```

(Tested with `TREZOR_MODEL=T3T1 PYOPT=1 make -C core build_firmware`)

[no changelog]
This commit is contained in:
Roman Zeyde 2025-04-23 10:02:43 +03:00 committed by Roman Zeyde
parent 65e8f96428
commit 09a323e578
10 changed files with 118 additions and 66 deletions

View File

@ -226,7 +226,7 @@ jobs:
with: with:
submodules: recursive submodules: recursive
- uses: ./.github/actions/environment - uses: ./.github/actions/environment
- run: nix-shell --run "poetry run make -C core build_unix_frozen" - run: nix-shell --run "poetry run make -C core build_unix"
# Ensure that "cargo build" works when NOT executed through our makefiles, # Ensure that "cargo build" works when NOT executed through our makefiles,
# indicating that it does not rely on particular envvars or other flags. # indicating that it does not rely on particular envvars or other flags.
# This makes sure that rust-analyzer will work on our codebase. # This makes sure that rust-analyzer will work on our codebase.

View File

@ -137,6 +137,24 @@ MAKO_FILTERS = {
"black_repr": black_repr_filter, "black_repr": black_repr_filter,
} }
ALTCOIN_PREFIXES = (
"binance",
"cardano",
"eos",
"ethereum",
"fido",
"monero",
"nem",
"nostr",
"ripple",
"solana",
"stellar",
"tezos",
"u2f",
)
DEBUG_PREFIXES = ("debug",)
def render_file( def render_file(
src: Path, dst: Path, coins: CoinsInfo, support_info: SupportInfo, models: list[str] src: Path, dst: Path, coins: CoinsInfo, support_info: SupportInfo, models: list[str]
@ -156,6 +174,8 @@ def render_file(
ethereum_defs_timestamp=int(eth_defs_date.timestamp()), ethereum_defs_timestamp=int(eth_defs_date.timestamp()),
THIS_FILE=this_file, THIS_FILE=this_file,
ROOT=ROOT, ROOT=ROOT,
ALTCOIN_PREFIXES=ALTCOIN_PREFIXES,
DEBUG_PREFIXES=DEBUG_PREFIXES,
**coins, **coins,
**MAKO_FILTERS, **MAKO_FILTERS,
ALL_MODELS=models, ALL_MODELS=models,

View File

@ -506,6 +506,8 @@ PROTO_SOURCES_DIR = '../../../common/protob/'
exclude_list = [PROTO_SOURCES_DIR + 'messages-bootloader.proto'] exclude_list = [PROTO_SOURCES_DIR + 'messages-bootloader.proto']
if not THP: if not THP:
exclude_list.append(PROTO_SOURCES_DIR + 'messages-thp.proto') exclude_list.append(PROTO_SOURCES_DIR + 'messages-thp.proto')
if PYOPT != '0':
exclude_list.append(PROTO_SOURCES_DIR + 'messages-debug.proto')
PROTO_SOURCES = Glob(PROTO_SOURCES_DIR + '*.proto', PROTO_SOURCES = Glob(PROTO_SOURCES_DIR + '*.proto',
exclude=exclude_list exclude=exclude_list
@ -602,12 +604,14 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py',
exclude=[ exclude=(
SOURCE_PY_DIR + 'storage/sd_salt.py', ([SOURCE_PY_DIR + 'storage/sd_salt.py'] if not SDCARD else []) +
] if not SDCARD else [] ([SOURCE_PY_DIR + 'storage/debug.py'] if PYOPT != '0' else [])
)
)) ))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py'))
SOURCE_PY_DEBUG_ENUMS = [SOURCE_PY_DIR + 'trezor/enums/Debug*.py']
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py',
exclude=[ exclude=[
SOURCE_PY_DIR + 'trezor/enums/Binance*.py', SOURCE_PY_DIR + 'trezor/enums/Binance*.py',
@ -622,7 +626,7 @@ if FROZEN:
SOURCE_PY_DIR + 'trezor/enums/Stellar*.py', SOURCE_PY_DIR + 'trezor/enums/Stellar*.py',
SOURCE_PY_DIR + 'trezor/enums/Tezos*.py', SOURCE_PY_DIR + 'trezor/enums/Tezos*.py',
SOURCE_PY_DIR + 'trezor/enums/Zcash*.py', SOURCE_PY_DIR + 'trezor/enums/Zcash*.py',
]) ] + (SOURCE_PY_DEBUG_ENUMS if PYOPT != '0' else []))
) )
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/*.py'))
@ -631,6 +635,7 @@ if FROZEN:
SOURCE_PY_DIR + 'apps/common/sdcard.py', SOURCE_PY_DIR + 'apps/common/sdcard.py',
] if not SDCARD else [] ] if not SDCARD else []
)) ))
if PYOPT == '0':
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py',

View File

@ -559,6 +559,8 @@ PROTO_SOURCES_DIR = '../../../common/protob/'
exclude_list = [PROTO_SOURCES_DIR + 'messages-bootloader.proto'] exclude_list = [PROTO_SOURCES_DIR + 'messages-bootloader.proto']
if not THP: if not THP:
exclude_list.append(PROTO_SOURCES_DIR + 'messages-thp.proto') exclude_list.append(PROTO_SOURCES_DIR + 'messages-thp.proto')
if PYOPT != '0':
exclude_list.append(PROTO_SOURCES_DIR + 'messages-debug.proto')
PROTO_SOURCES = Glob(PROTO_SOURCES_DIR + '*.proto', PROTO_SOURCES = Glob(PROTO_SOURCES_DIR + '*.proto',
exclude=exclude_list exclude=exclude_list
@ -656,12 +658,14 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/wire/codec/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'storage/*.py',
exclude=[ exclude=(
SOURCE_PY_DIR + 'storage/sd_salt.py', ([SOURCE_PY_DIR + 'storage/sd_salt.py'] if 'sd_card' not in FEATURES_AVAILABLE else []) +
] if 'sd_card' not in FEATURES_AVAILABLE else [] ([SOURCE_PY_DIR + 'storage/debug.py'] if PYOPT != '0' else [])
)
)) ))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/messages/__init__.py'))
SOURCE_PY_DEBUG_ENUMS = [SOURCE_PY_DIR + 'trezor/enums/Debug*.py']
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/enums/*.py',
exclude=[ exclude=[
SOURCE_PY_DIR + 'trezor/enums/Binance*.py', SOURCE_PY_DIR + 'trezor/enums/Binance*.py',
@ -676,7 +680,7 @@ if FROZEN:
SOURCE_PY_DIR + 'trezor/enums/Stellar*.py', SOURCE_PY_DIR + 'trezor/enums/Stellar*.py',
SOURCE_PY_DIR + 'trezor/enums/Tezos*.py', SOURCE_PY_DIR + 'trezor/enums/Tezos*.py',
SOURCE_PY_DIR + 'trezor/enums/Zcash*.py', SOURCE_PY_DIR + 'trezor/enums/Zcash*.py',
]) ] + (SOURCE_PY_DEBUG_ENUMS if PYOPT != '0' else []))
) )
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/*.py'))
@ -685,6 +689,7 @@ if FROZEN:
SOURCE_PY_DIR + 'apps/common/sdcard.py', SOURCE_PY_DIR + 'apps/common/sdcard.py',
] if "sd_card" not in FEATURES_AVAILABLE else [] ] if "sd_card" not in FEATURES_AVAILABLE else []
)) ))
if PYOPT == '0':
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/debug/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py')) SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/homescreen/*.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py', SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'apps/management/*.py',

View File

@ -235,8 +235,6 @@ static void _librust_qstrs(void) {
MP_QSTR_danger; MP_QSTR_danger;
MP_QSTR_data_hash; MP_QSTR_data_hash;
MP_QSTR_data_len; MP_QSTR_data_len;
MP_QSTR_debug__loading_seed;
MP_QSTR_debug__loading_seed_not_recommended;
MP_QSTR_decode; MP_QSTR_decode;
MP_QSTR_deinit; MP_QSTR_deinit;
MP_QSTR_description; MP_QSTR_description;
@ -366,7 +364,6 @@ static void _librust_qstrs(void) {
MP_QSTR_modify_fee__transaction_fee; MP_QSTR_modify_fee__transaction_fee;
MP_QSTR_more_info_callback; MP_QSTR_more_info_callback;
MP_QSTR_multiple_pages_texts; MP_QSTR_multiple_pages_texts;
MP_QSTR_nostr__event_kind_template;
MP_QSTR_notification; MP_QSTR_notification;
MP_QSTR_notification_level; MP_QSTR_notification_level;
MP_QSTR_page_count; MP_QSTR_page_count;
@ -1130,6 +1127,7 @@ static void _librust_qstrs(void) {
MP_QSTR_nem__under_namespace; MP_QSTR_nem__under_namespace;
MP_QSTR_nem__unencrypted; MP_QSTR_nem__unencrypted;
MP_QSTR_nem__unknown_mosaic; MP_QSTR_nem__unknown_mosaic;
MP_QSTR_nostr__event_kind_template;
MP_QSTR_ripple__confirm_tag; MP_QSTR_ripple__confirm_tag;
MP_QSTR_ripple__destination_tag_template; MP_QSTR_ripple__destination_tag_template;
MP_QSTR_solana__account_index; MP_QSTR_solana__account_index;
@ -1245,4 +1243,8 @@ static void _librust_qstrs(void) {
MP_QSTR_u2f__title_get; MP_QSTR_u2f__title_get;
MP_QSTR_u2f__title_set; MP_QSTR_u2f__title_set;
#endif #endif
#if !PYOPT
MP_QSTR_debug__loading_seed;
MP_QSTR_debug__loading_seed_not_recommended;
#endif
} }

View File

@ -13,21 +13,6 @@ from typing import Union, Set
RUST_SRC = THIS_FILE.parent / "src" RUST_SRC = THIS_FILE.parent / "src"
ALTCOIN_PREFIXES = (
"binance",
"cardano",
"eos",
"ethereum",
"fido",
"monero",
"nem",
"ripple",
"solana",
"stellar",
"tezos",
"u2f",
)
def find_unique_patterns_in_dir(directory: Union[str, Path], pattern: str) -> Set[str]: def find_unique_patterns_in_dir(directory: Union[str, Path], pattern: str) -> Set[str]:
command = f"grep -ro '{pattern}' {directory}" command = f"grep -ro '{pattern}' {directory}"
result = subprocess.run(command, stdout=subprocess.PIPE, text=True, shell=True) result = subprocess.run(command, stdout=subprocess.PIPE, text=True, shell=True)
@ -42,12 +27,18 @@ for prefix in ALTCOIN_PREFIXES:
mp_prefix = f"MP_QSTR_{prefix}__" mp_prefix = f"MP_QSTR_{prefix}__"
qstrings_universal |= {qstr for qstr in qstrings if qstr.startswith(mp_prefix)} qstrings_universal |= {qstr for qstr in qstrings if qstr.startswith(mp_prefix)}
qstrings_btconly = qstrings - qstrings_universal qstrings_debug = set()
for prefix in DEBUG_PREFIXES:
mp_prefix = f"MP_QSTR_{prefix}__"
qstrings_debug |= {qstr for qstr in qstrings if qstr.startswith(mp_prefix)}
qstrings_btconly = qstrings - qstrings_universal - qstrings_debug
# sort result alphabetically # sort result alphabetically
digits = range(10) digits = range(10)
qstrings_btconly_sorted = sorted(qstrings_btconly) qstrings_btconly_sorted = sorted(qstrings_btconly)
qstrings_universal_sorted = sorted(qstrings_universal) qstrings_universal_sorted = sorted(qstrings_universal)
qstrings_debug_sorted = sorted(qstrings_debug)
%>\ %>\
% for digit in digits: % for digit in digits:
MP_QSTR_${digit}; MP_QSTR_${digit};
@ -60,4 +51,9 @@ qstrings_universal_sorted = sorted(qstrings_universal)
${qstr}; ${qstr};
% endfor % endfor
#endif #endif
#if !PYOPT
% for qstr in qstrings_debug_sorted:
${qstr};
% endfor
#endif
} }

View File

@ -354,7 +354,9 @@ pub enum TranslatedString {
confirm_total__sending_from_account = 221, // "Sending from account:" confirm_total__sending_from_account = 221, // "Sending from account:"
confirm_total__title_fee = 222, // "Fee info" confirm_total__title_fee = 222, // "Fee info"
confirm_total__title_sending_from = 223, // "Sending from" confirm_total__title_sending_from = 223, // "Sending from"
#[cfg(feature = "debug")]
debug__loading_seed = 224, // "Loading seed" debug__loading_seed = 224, // "Loading seed"
#[cfg(feature = "debug")]
debug__loading_seed_not_recommended = 225, // "Loading private seed is not recommended." debug__loading_seed_not_recommended = 225, // "Loading private seed is not recommended."
device_name__change_template = 226, // "Change device name to {0}?" device_name__change_template = 226, // "Change device name to {0}?"
device_name__title = 227, // "Device name" device_name__title = 227, // "Device name"
@ -1757,7 +1759,9 @@ impl TranslatedString {
Self::confirm_total__sending_from_account => "Sending from account:", Self::confirm_total__sending_from_account => "Sending from account:",
Self::confirm_total__title_fee => "Fee info", Self::confirm_total__title_fee => "Fee info",
Self::confirm_total__title_sending_from => "Sending from", Self::confirm_total__title_sending_from => "Sending from",
#[cfg(feature = "debug")]
Self::debug__loading_seed => "Loading seed", Self::debug__loading_seed => "Loading seed",
#[cfg(feature = "debug")]
Self::debug__loading_seed_not_recommended => "Loading private seed is not recommended.", Self::debug__loading_seed_not_recommended => "Loading private seed is not recommended.",
Self::device_name__change_template => "Change device name to {0}?", Self::device_name__change_template => "Change device name to {0}?",
Self::device_name__title => "Device name", Self::device_name__title => "Device name",
@ -3159,7 +3163,9 @@ impl TranslatedString {
Qstr::MP_QSTR_confirm_total__sending_from_account => Some(Self::confirm_total__sending_from_account), Qstr::MP_QSTR_confirm_total__sending_from_account => Some(Self::confirm_total__sending_from_account),
Qstr::MP_QSTR_confirm_total__title_fee => Some(Self::confirm_total__title_fee), Qstr::MP_QSTR_confirm_total__title_fee => Some(Self::confirm_total__title_fee),
Qstr::MP_QSTR_confirm_total__title_sending_from => Some(Self::confirm_total__title_sending_from), Qstr::MP_QSTR_confirm_total__title_sending_from => Some(Self::confirm_total__title_sending_from),
#[cfg(feature = "debug")]
Qstr::MP_QSTR_debug__loading_seed => Some(Self::debug__loading_seed), Qstr::MP_QSTR_debug__loading_seed => Some(Self::debug__loading_seed),
#[cfg(feature = "debug")]
Qstr::MP_QSTR_debug__loading_seed_not_recommended => Some(Self::debug__loading_seed_not_recommended), Qstr::MP_QSTR_debug__loading_seed_not_recommended => Some(Self::debug__loading_seed_not_recommended),
Qstr::MP_QSTR_device_name__change_template => Some(Self::device_name__change_template), Qstr::MP_QSTR_device_name__change_template => Some(Self::device_name__change_template),
Qstr::MP_QSTR_device_name__title => Some(Self::device_name__title), Qstr::MP_QSTR_device_name__title => Some(Self::device_name__title),

View File

@ -7,22 +7,6 @@
import json import json
import re import re
ALTCOIN_PREFIXES = (
"binance",
"cardano",
"eos",
"ethereum",
"fido",
"monero",
"nem",
"nostr",
"ripple",
"solana",
"stellar",
"tezos",
"u2f",
)
TR_DIR = ROOT / "core" / "translations" TR_DIR = ROOT / "core" / "translations"
order_file = TR_DIR / "order.json" order_file = TR_DIR / "order.json"
@ -53,6 +37,9 @@ pub enum TranslatedString {
%if any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES): %if any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES):
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
%endif %endif
%if any(name.startswith(prefix + "__") for prefix in DEBUG_PREFIXES):
#[cfg(feature = "debug")]
%endif
${name} = ${idx}, // ${encode_str(en_data.get(name))} ${name} = ${idx}, // ${encode_str(en_data.get(name))}
% endfor % endfor
} }
@ -69,11 +56,15 @@ impl TranslatedString {
continue continue
layouts_dict = value if isinstance(value, dict) else None layouts_dict = value if isinstance(value, dict) else None
universal_fw = any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES) universal_fw = any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES)
is_debug = any(name.startswith(prefix + "__") for prefix in DEBUG_PREFIXES)
%>\ %>\
%if layouts_dict is not None: %if layouts_dict is not None:
% for layout_name, layout_value in layouts_dict.items(): % for layout_name, layout_value in layouts_dict.items():
%if universal_fw: %if universal_fw:
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
%endif
%if is_debug:
#[cfg(feature = "debug")]
%endif %endif
#[cfg(feature = "${f"layout_{layout_name.lower()}"}")] #[cfg(feature = "${f"layout_{layout_name.lower()}"}")]
Self::${name} => ${encode_str(layout_value)}, Self::${name} => ${encode_str(layout_value)},
@ -81,6 +72,9 @@ impl TranslatedString {
%else: %else:
%if universal_fw: %if universal_fw:
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
%endif
%if is_debug:
#[cfg(feature = "debug")]
%endif %endif
Self::${name} => ${encode_str(value)}, Self::${name} => ${encode_str(value)},
%endif %endif
@ -99,6 +93,9 @@ impl TranslatedString {
%if any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES): %if any(name.startswith(prefix + "__") for prefix in ALTCOIN_PREFIXES):
#[cfg(feature = "universal_fw")] #[cfg(feature = "universal_fw")]
%endif %endif
%if any(name.startswith(prefix + "__") for prefix in DEBUG_PREFIXES):
#[cfg(feature = "debug")]
%endif
Qstr::MP_QSTR_${name} => Some(Self::${name}), Qstr::MP_QSTR_${name} => Some(Self::${name}),
% endfor % endfor
_ => None, _ => None,

View File

@ -82,26 +82,27 @@ SignIdentity = 53
SignedIdentity = 54 SignedIdentity = 54
GetECDHSessionKey = 61 GetECDHSessionKey = 61
ECDHSessionKey = 62 ECDHSessionKey = 62
DebugLinkDecision = 100
DebugLinkGetState = 101
DebugLinkState = 102
DebugLinkStop = 103
DebugLinkLog = 104
DebugLinkMemoryRead = 110
DebugLinkMemory = 111
DebugLinkMemoryWrite = 112
DebugLinkFlashErase = 113
DebugLinkLayout = 9001
DebugLinkReseedRandom = 9002
DebugLinkRecordScreen = 9003
DebugLinkEraseSdCard = 9005
DebugLinkWatchLayout = 9006
DebugLinkResetDebugEvents = 9007
DebugLinkOptigaSetSecMax = 9008
BenchmarkListNames = 9100 BenchmarkListNames = 9100
BenchmarkNames = 9101 BenchmarkNames = 9101
BenchmarkRun = 9102 BenchmarkRun = 9102
BenchmarkResult = 9103 BenchmarkResult = 9103
if __debug__:
DebugLinkDecision = 100
DebugLinkGetState = 101
DebugLinkState = 102
DebugLinkStop = 103
DebugLinkLog = 104
DebugLinkMemoryRead = 110
DebugLinkMemory = 111
DebugLinkMemoryWrite = 112
DebugLinkFlashErase = 113
DebugLinkLayout = 9001
DebugLinkReseedRandom = 9002
DebugLinkRecordScreen = 9003
DebugLinkEraseSdCard = 9005
DebugLinkWatchLayout = 9006
DebugLinkResetDebugEvents = 9007
DebugLinkOptigaSetSecMax = 9008
if not utils.BITCOIN_ONLY: if not utils.BITCOIN_ONLY:
SetU2FCounter = 63 SetU2FCounter = 63
GetNextU2FCounter = 80 GetNextU2FCounter = 80

View File

@ -6,12 +6,32 @@
from trezor import utils from trezor import utils
% endif % endif
% for value in values_always: <%
values_debug = [v for v in values_always if v.name.startswith('DebugLink')]
values_nondebug = [v for v in values_always if not v.name.startswith('DebugLink')]
%>\
% for value in values_nondebug:
${value.name} = ${value.number} ${value.name} = ${value.number}
% endfor % endfor
% if values_altcoin: % if values_debug:
if not utils.BITCOIN_ONLY: if __debug__:
% for value in values_altcoin: % for value in values_debug:
${value.name} = ${value.number} ${value.name} = ${value.number}
% endfor % endfor
% endif % endif
% if values_altcoin:
if not utils.BITCOIN_ONLY:
<%
values_debug = [v for v in values_altcoin if v.name.startswith('DebugLink')]
values_nondebug = [v for v in values_altcoin if not v.name.startswith('DebugLink')]
%>\
% for value in values_nondebug:
${value.name} = ${value.number}
% endfor
% if values_debug:
if __debug__:
% for value in values_debug:
${value.name} = ${value.number}
% endfor
% endif
% endif