Files
libretiny/platform.py
2022-06-26 16:00:55 +02:00

266 lines
9.7 KiB
Python

# Copyright (c) Kuba Szczodrzyński 2022-04-20.
import json
import sys
from os.path import dirname, join
from typing import Dict
from platformio import util
from platformio.debug.config.base import DebugConfigBase
from platformio.debug.exception import DebugInvalidOptionsError
from platformio.managers.platform import PlatformBase
from platformio.package.exception import MissingPackageManifestError
from platformio.package.manager.base import BasePackageManager
from platformio.package.meta import PackageItem, PackageSpec
from platformio.platform.board import PlatformBoardConfig
# Make tools available
sys.path.insert(0, dirname(__file__))
from tools.util.platform import get_board_manifest
# Remove current dir so it doesn't conflict with PIO
sys.path.remove(dirname(__file__))
libretuya_packages = None
manifest_default = {"version": "0.0.0", "description": "", "keywords": []}
def load_manifest(self, src):
try:
return BasePackageManager._load_manifest(self, src)
except MissingPackageManifestError:
# ignore all exceptions
pass
# get the installation temporary path
path = src.path if isinstance(src, PackageItem) else src
# raise the exception if this package is not from libretuya
if (
not hasattr(self, "spec_map")
or path not in self.spec_map
or not libretuya_packages
):
raise MissingPackageManifestError(", ".join(self.manifest_names))
# get the saved spec
spec: PackageSpec = self.spec_map[path]
# read package data from platform.json
manifest: dict = libretuya_packages[spec.name]
# find additional manifest info
manifest = manifest.get("manifest", manifest_default)
# extract tag version
url = getattr(spec, "url", None) or getattr(spec, "uri", None) or ""
if "#" in url:
manifest["version"] = url.rpartition("#")[2].lstrip("v")
# put info from spec
manifest.update(
{
"name": spec.name,
"repository": {
"type": "git",
"url": url,
},
}
)
# save in cache
cache_key = "load_manifest-%s" % path
self.memcache_set(cache_key, manifest)
# result = ManifestParserFactory.new(json.dumps(manifest), ManifestFileType.PACKAGE_JSON).as_dict()
with open(join(path, self.manifest_names[0]), "w") as f:
json.dump(manifest, f)
return manifest
def find_pkg_root(self, path: str, spec: PackageSpec):
try:
return BasePackageManager._find_pkg_root(self, path, spec)
except MissingPackageManifestError as e:
# raise the exception if this package is not from libretuya
if not libretuya_packages or spec.name not in libretuya_packages:
raise e
# save the spec for later
if not hasattr(self, "spec_map"):
self.spec_map = {}
self.spec_map[path] = spec
return path
class LibretuyaPlatform(PlatformBase):
boards_base: Dict[str, dict] = {}
custom_opts: Dict[str, object] = {}
def configure_default_packages(self, options, targets):
# patch find_pkg root to ignore missing manifests and save PackageSpec
if not hasattr(BasePackageManager, "_find_pkg_root"):
BasePackageManager._find_pkg_root = BasePackageManager.find_pkg_root
BasePackageManager.find_pkg_root = find_pkg_root
# patch load_manifest to generate manifests from PackageSpec
if not hasattr(BasePackageManager, "_load_manifest"):
BasePackageManager._load_manifest = BasePackageManager.load_manifest
BasePackageManager.load_manifest = load_manifest
pioframework = options.get("pioframework")
if not pioframework:
return
framework = pioframework[0]
# allow using "arduino" as framework
if framework == "arduino":
board = self.get_boards(options.get("board"))
frameworks = board.get("frameworks")
framework = next(fw for fw in frameworks if framework in fw)
options.get("pioframework")[0] = framework
# make ArduinoCore-API required
if "arduino" in framework:
self.packages["framework-arduino-api"]["optional"] = False
framework_obj = self.frameworks[framework]
if "package" in framework_obj:
package_obj = self.packages[framework_obj["package"]]
else:
package_obj = {}
# set specific compiler versions
if "toolchain" in package_obj:
(toolchain, version) = package_obj["toolchain"].split("@")
self.packages[f"toolchain-{toolchain}"]["version"] = version
# require bk7231tools
if "beken-72xx" in framework:
self.packages["tool-bk7231tools"]["optional"] = False
# mark framework SDK as required
package_obj["optional"] = False
# gather library dependencies
libraries = package_obj["libraries"] if "libraries" in package_obj else {}
for name, package in self.packages.items():
if "optional" in package and package["optional"]:
continue
if "libraries" not in package:
continue
libraries.update(package["libraries"])
# use appropriate vendor library versions
packages_new = {}
for name, package in self.packages.items():
if not name.startswith("library-"):
continue
name = name[8:] # strip "library-"
if name not in libraries:
continue
lib_version = libraries[name][-1] # get latest version tag
package = dict(**package) # clone the base package
package["version"] = (
package["base_url"] + "#" + lib_version
) # use the specific version
package["optional"] = False # make it required
lib_version = lib_version.lstrip("v") # strip "v" in target name
name = f"library-{name}@{lib_version}"
packages_new[name] = package # put the package under a new name
self.packages.update(packages_new)
# save platform packages for later
global libretuya_packages
libretuya_packages = self.packages
# save custom options from env
self.custom_opts = {}
for key, value in options.items():
if not key.startswith("custom_"):
continue
self.custom_opts[key[7:]] = value
return super().configure_default_packages(options, targets)
def custom(self, key: str) -> object:
return self.custom_opts.get(key, None)
def get_boards(self, id_=None):
result = PlatformBase.get_boards(self, id_)
if not result:
return result
if id_:
return self.update_board(result)
else:
for key, value in result.items():
result[key] = self.update_board(value)
return result
def update_board(self, board: PlatformBoardConfig):
if "_base" in board:
board._manifest = get_board_manifest(board._manifest)
# add "arduino" framework
has_arduino = any("arduino" in fw for fw in board.manifest["frameworks"])
if has_arduino:
board.manifest["frameworks"].append("arduino")
# inspired by platform-ststm32/platform.py
debug = board.manifest.get("debug", {})
if not debug:
return board
protocols = debug.get("protocols", [])
if "tools" not in debug:
debug["tools"] = {}
if "custom" not in debug["tools"]:
debug["tools"]["custom"] = {}
init = debug.get("gdb_init", [])
init += ["set mem inaccessible-by-default off"]
for link in protocols:
if link == "openocd":
args = ["-s", "$PACKAGE_DIR/scripts"]
if debug.get("openocd_config"):
args.extend(
[
"-f",
"$LTPATH/platform/$LTFAMILY/openocd/%s"
% debug.get("openocd_config"),
]
)
debug["tools"][link] = {
"server": {
"package": "tool-openocd",
"executable": "bin/openocd",
"arguments": args,
},
"extra_cmds": init,
}
else:
continue
debug["tools"][link]["default"] = link == debug.get("protocol", "")
debug["tools"]["custom"]["extra_cmds"] = init
board.manifest["debug"] = debug
return board
def configure_debug_session(self, debug_config: DebugConfigBase):
opts = debug_config.env_options
server = debug_config.server
lt_path = dirname(__file__)
lt_family = opts["framework"][0].rpartition("-")[0]
if not server:
debug_tool = opts.get("debug_tool", "custom")
board = opts.get("board", "<unknown>")
if debug_tool == "custom":
return
exc = DebugInvalidOptionsError(
f"[LibreTuya] Debug tool {debug_tool} is not supported by board {board}."
)
exc.MESSAGE = ""
raise exc
if "arguments" in server:
# allow setting interface via platformio.ini
if opts.get("openocd_interface"):
server["arguments"] = [
"-f",
"interface/%s.cfg" % opts.get("openocd_interface"),
] + server["arguments"]
# replace $LTFAMILY with actual name
server["arguments"] = [
arg.replace("$LTFAMILY", lt_family).replace("$LTPATH", lt_path)
for arg in server["arguments"]
]