Skip to content

Commit

Permalink
add create_app_list
Browse files Browse the repository at this point in the history
  • Loading branch information
sgliner-ledger committed Aug 23, 2023
1 parent 2bdd963 commit c02da85
Show file tree
Hide file tree
Showing 8 changed files with 694 additions and 0 deletions.
2 changes: 2 additions & 0 deletions create_app_list/.gitconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[url "https://github.com/"]
insteadOf = ssh://[email protected]/
175 changes: 175 additions & 0 deletions create_app_list/app_load_params_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python3

from argparse import ArgumentParser
from pathlib import Path
from typing import Dict
from collections import defaultdict
import json

APP_LOAD_PARAMS_ALLOWED = {
"targetId",
"targetVersion",
"apiLevel",
"fileName",
"icon",
"curve",
"path",
"path_slip21",
"appName",
# "signature", # Reserved for internal usage
# "signApp", # Reserved for internal usage
"appFlags",
# "bootAddr", # Deprecated?
# "rootPrivateKey", # Should not be used for app deployment
# "signPrivateKey", # Should not be used for app deployment
# "apdu", # Should not be used for app deployment
# "deployLegacy", # Deprecated?
"delete",
# "params", # Deprecated?
"tlv",
"dataSize",
"appVersion",
# "offline", # Should not be used for app deployment
# "offlineText", # Should not be used for app deployment
# "installparamsSize", # Deprecated?
"tlvraw",
# "dep", # Deprecated?
"nocrc",
}

APP_LOAD_PARAMS_VALUE_CHECK = {
"curve",
"path",
"path_slip21",
"appName",
"appFlags",
}


def parse_listapploadparams(app_load_params_str: str) -> Dict:
# Convert to dict. Store value in list type as some params can appear
# multiple times (e.g `--path`).

app_load_params = defaultdict(list)
for param in app_load_params_str.split("--"):
param = param.strip()
if not param:
continue

if param.startswith("targetVersion="):
parts = param.split("=")
else:
parts = param.split(" ")

param_name = parts[0]

param_value = None
if len(parts) > 1:
param_value = " ".join(parts[1:])

app_load_params[param_name].append(param_value)

return dict(app_load_params)


def check_manifest(manifest: dict, database: dict) -> None:
ret = 0

for variant, data in manifest["VARIANTS"].items():
target = data["TARGET"]
print(f"Checking for target '{target}' and variant '{variant}'")

app_load_params_str = data["APP_LOAD_PARAMS"]
app_load_params = parse_listapploadparams(app_load_params_str)
print("Retrieved listapploadparams:")
print(json.dumps(app_load_params, indent=4))

# Check that no unknown or reserved param is used
for key in app_load_params:
if key not in APP_LOAD_PARAMS_ALLOWED:
print(f"[ERROR] Not allowed '{key}' in APP_LOAD_PARAMS")
ret = -1

# Retrieve database app_params
app_params_ref = database.get(variant)
if not app_params_ref:
print(f"[ERROR] Missing '{variant}' definition in the database")
ret = -1
break

# Check that the params match with the one from the database
for key in APP_LOAD_PARAMS_VALUE_CHECK:
app_params_ref_value = app_params_ref.get(key)
app_load_params_value = app_load_params.get(key)
if key == "appName":
if len(app_load_params_value) != 1:
print(f"[ERROR] Expected a single value for 'appName' ({app_load_params_value} vs {app_params_ref_value})")
ret = -1
continue
app_load_params_value = app_load_params_value[0]
elif key == "appFlags":
if not app_load_params_value:
app_load_params_value = ["0x000"]

if len(app_load_params_value) != 1:
print(f"[ERROR] Expected a single value for 'appFlags' ({app_load_params_value} vs {app_params_ref_value})")
ret = -1
continue

app_load_params_value = app_load_params_value[0]
if app_load_params_value.startswith("0x"):
app_load_params_value = int(app_load_params_value, 16)
else:
app_load_params_value = int(app_load_params_value)

app_params_ref_value = app_params_ref_value.get(target)
if not app_params_ref_value:
print(f"[ERROR] Missing 'appFlags' for '{target}'")
ret = -1
continue
if app_params_ref_value.startswith("0x"):
app_params_ref_value = int(app_params_ref_value, 16)
else:
app_params_ref_value = int(app_params_ref_value)

if not app_load_params_value == app_params_ref_value:
print(f"[ERROR] Unexpected value for '{key}' ({app_load_params_value} vs {app_params_ref_value})")
ret = -1

return ret


def check_app(app_manifests_path: Path, database_path: Path) -> None:
ret = 0

# Retrieve database
with open(database_path, 'r') as f:
database = json.load(f)

manifest_list = [x for x in app_manifests_path.iterdir() if x.name.endswith(".json")]
for manifest_path in manifest_list:
# Retrieve manifest
with open(manifest_path, 'r') as f:
manifest = json.load(f)

print(f"Checking {manifest_path.name}")
ret |= check_manifest(manifest, database)

if ret:
print("Please fix the issues by either:")
print("- Updating your app Makefile")
print("- Creating a PR on https://github.com/LedgerHQ/ledger-app-database"
" to update the app-params-database.json")

exit(ret)


if __name__ == "__main__":
parser = ArgumentParser()

parser.add_argument("--app_manifests_path", required=True, type=Path)
parser.add_argument("--database_path", required=True, type=Path)

args = parser.parse_args()

check_app(args.app_manifests_path, args.database_path)
138 changes: 138 additions & 0 deletions create_app_list/app_load_params_gen_db_full.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env python3

from argparse import ArgumentParser
from pathlib import Path
from app_load_params_utils import load_database, save_database
from makefile_dump import get_app_listvariants, get_app_listparams
from app_load_params_check import APP_LOAD_PARAMS_VALUE_CHECK, parse_listapploadparams
import json
from collections import namedtuple

Models = namedtuple('Models', ['sdk_value', 'device_name'])

MODELS = [Models("$NANOS_SDK", "nanos"),
Models("$NANOX_SDK", "nanox"),
Models("$NANOSP_SDK", "nanosp"),
Models("$STAX_SDK", "stax")]


BUILD_PATH_LIST = {
"app-acala" : "app" ,
"app-algorand" : "app" ,
"app-avalanche" : "app" ,
"app-arweave" : "app" ,
"app-alephzero" : "app" ,
"app-astar" : "app" ,
"app-axelar" : "app" ,
"app-bifrost" : "app" ,
"app-bifrost-kusama" : "app" ,
"app-bifrost-new" : "app" ,
"app-blockstack" : "app" ,
"app-coti" : "app" ,
"app-casper" : "app" ,
"app-centrifuge" : "app" ,
"app-cosmos" : "app" ,
"app-cryptocom" : "app" ,
"app-dgld" : "app" ,
"app-decimal" : "app" ,
"app-desmos" : "app" ,
"app-dock" : "app" ,
"app-edgeware" : "app" ,
"app-equilibrium" : "app" ,
"app-filecoin" : "app" ,
"app-firmachain" : "app" ,
"app-flow" : "app" ,
"app-genshiro" : "app" ,
"app-iov" : "app" ,
"app-internetcomputer" : "app" ,
"app-karura" : "app" ,
"app-khala" : "app" ,
"app-kusama" : "app" ,
"app-medibloc" : "app" ,
"app-near" : "workdir/app-near" ,
"app-nodle" : "app" ,
"app-oasis" : "app" ,
"app-panacea" : "app" ,
"app-parallel" : "app" ,
"app-persistence" : "app" ,
"app-phala" : "app" ,
"app-polkadex" : "app" ,
"app-polkadot" : "app" ,
"app-polymesh" : "app" ,
"app-reef" : "app" ,
"app-secret" : "app" ,
"app-stacks" : "app" ,
"app-statemine" : "app" ,
"app-statemint" : "app" ,
"app-thorchain" : "app" ,
"app-terra" : "app" ,
"app-xxnetwork" : "app" ,
}


def gen_variant(app_name: str, output_file: Path = "", workdir: Path = "") -> dict:
print(f"Generating for {app_name}")

app_build_path = BUILD_PATH_LIST.get(app_name, "./")
if app_build_path != "./":
app_full_path = workdir / app_name / app_build_path
else:
app_full_path = workdir / app_name

# Retrieve database

database_params = {
"name": app_name,
}

# Retrieve available variants
for model in MODELS:
try:
variant_param_name, variants = get_app_listvariants(app_full_path, model.sdk_value, allow_failure=True)
except:
print("Skipping generation due to error")
continue

database_params["variant_param"] = variant_param_name
database_params["variants_" + model.device_name] = variants
if app_build_path != "./":
database_params["build_path"] = app_build_path

return database_params


def gen_all_variants(config: str, output_file: Path = "", workdir: Path = "") -> str:
output = []
for key in config:
if key["name"]:
output.append(gen_variant(key["name"], output_file, workdir))
return output


def get_variants(input_file: Path = "", input_list: str = "", output_file: Path = "", workdir: Path = "") -> str:
if input_file:
config = load_database(input_file)
else:
config = input_list

output = gen_all_variants(config, output_file, workdir)

if output_file:
save_database(output, output_file)

return output


if __name__ == "__main__":
parser = ArgumentParser()

parser.add_argument("--config_file", required=True, type=Path)
parser.add_argument("--database_path", required=True, type=Path)

args = parser.parse_args()

if args.config_file is not None:
config = load_database(args.config_file)
gen_all_variants(config, args.database_path)
else:
parser.print_help()
38 changes: 38 additions & 0 deletions create_app_list/app_load_params_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3

from pathlib import Path
import json


def format_database(database: dict) -> str:
database_str = json.dumps(database, indent=2, sort_keys=True)
# Drop some newlines to compact a bit the data while still
# making it readable.
database_str = database_str.replace("[\n ", "[")
database_str = database_str.replace("{\n ", "{")
database_str = database_str.replace("\n ]", "]")
database_str = database_str.replace("\n }", "}")
database_str = database_str.replace("\n ", " ")

# Add newline at the end of file
database_str += "\n"

return database_str


def load_database(database_path: Path):
database = {}
if database_path.exists():
with open(database_path, 'r') as f:
database = json.load(f)
else:
with open(database_path, 'w') as f:
print("File created:", database_path)
database = []
return database


def save_database(database: dict, database_path: Path):
database_str = format_database(database)
with open(database_path, 'w') as f:
f.write(database_str)
Loading

0 comments on commit c02da85

Please sign in to comment.