diff --git a/tools/Unix.mk b/tools/Unix.mk index e72ecc7743844..e6ee5c4b27ebb 100644 --- a/tools/Unix.mk +++ b/tools/Unix.mk @@ -162,6 +162,7 @@ all: $(BIN) .PHONY: context clean_context config oldconfig menuconfig nconfig qconfig gconfig export subdir_clean clean subdir_distclean distclean apps_clean apps_distclean .PHONY: pass1 pass1dep .PHONY: pass2 pass2dep +.PHONY: debug_info checkpython3 # Target used to copy include/nuttx/lib/math.h. If CONFIG_ARCH_MATH_H is # defined, then there is an architecture specific math.h header file @@ -618,6 +619,40 @@ bootloader: clean_bootloader: $(Q) $(MAKE) -C $(ARCH_SRC) clean_bootloader +checkpython3: + @if [ -z "$$(which python3)" ]; then \ + echo "ERROR: python3 not found in PATH"; \ + echo " Please install python3 or fix the PATH"; \ + exit 1; \ + fi + +# debug_info target flags to get diagnostic info without building nxdiag application + +SYSINFO_PARSE_FLAGS = "$(realpath $(TOPDIR))" +SYSINFO_PARSE_FLAGS += "-fsysinfo.h" + +SYSINFO_FLAGS = "-c" +SYSINFO_FLAGS += "-p" +SYSINFO_FLAGS += -f \""$(shell echo '$(CFLAGS)' | sed 's/"/\\\\\\"/g')"\" +SYSINFO_FLAGS += \""$(shell echo '$(CXXFLAGS)' | sed 's/"/\\\\\\"/g')"\" +SYSINFO_FLAGS += \""$(shell echo '$(LDFLAGS)' | sed 's/"/\\\\\\"/g')"\" + +ifneq ($(findstring esp,$(CONFIG_ARCH_CHIP)),) +ARCH_ESP_HALDIR = $(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)chip$(DELIM)esp-hal-3rdparty +SYSINFO_FLAGS += --espressif "$(TOPDIR)" "$(ARCH_ESP_HALDIR)" +SYSINFO_FLAGS += "--espressif_chip" +endif + +# debug_info: Parse nxdiag example output file (sysinfo.h) and print + +debug_info: checkpython3 + @if [[ ! -f "sysinfo.h" ]]; then \ + echo "file sysinfo.h not exists"; \ + python3 $(TOPDIR)$(DELIM)tools$(DELIM)host_sysinfo.py $(SYSINFO_FLAGS) \ + $(realpath $(TOPDIR)) > sysinfo.h; \ + fi + @python3 $(TOPDIR)$(DELIM)tools$(DELIM)parse_sysinfo.py $(SYSINFO_PARSE_FLAGS) + # pass1dep: Create pass1 build dependencies # pass2dep: Create pass2 build dependencies diff --git a/tools/host_sysinfo.py b/tools/host_sysinfo.py new file mode 100755 index 0000000000000..ae86f4296f548 --- /dev/null +++ b/tools/host_sysinfo.py @@ -0,0 +1,894 @@ +#!/usr/bin/env python3 +# tools/host_sysinfo.py +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import argparse +import os +import platform +import re +import subprocess +import sys + +# Custom argparse actions # + + +class validate_flags_arg(argparse.Action): + """ + Custom argparse action to validate the number of parameters passed to the + --flags argument. Exactly three parameters are required. + """ + + def __call__(self, parser, namespace, values, option_string=None): + if len(values) != 3: + raise argparse.ArgumentError( + self, + "Invalid number of parameters for --flags. Exactly three parameters are required.", + ) + setattr(namespace, self.dest, values) + + +# Vendor specific functions # + +# Espressif # + + +def run_espressif_tool(cmd): + tool = "esptool.py" + strings_out = "" + command = "{} {}".format(tool, cmd) + + strings_out = subprocess.run( + command, universal_newlines=True, shell=True, capture_output=True + ) + + return strings_out.stdout + + +def get_espressif_chip_id(): + output = run_espressif_tool("chip_id") + string_out = next((s for s in output.split("\n") if "Chip ID" in s), "Not found") + if string_out != "Not found": + string_out = string_out.split("Warning: ")[-1] + return string_out + + +def get_espressif_flash_id(): + strings_out = [] + output = run_espressif_tool("flash_id") + + strings_out.append( + next( + (s for s in output.split("\n") if "Manufacturer" in s), + "Manufacturer: Not found", + ) + ) + strings_out.append( + next((s for s in output.split("\n") if "Device" in s), "Device: Not found") + ) + + return strings_out + + +def get_espressif_security_info(): + output = run_espressif_tool("get_security_info") + + start_string = "=====================" + stop_string = "Hard resetting via RTS pin..." + output = output.split("\n") + strings_out = [] + + str_out = next((s for s in output if start_string in s), "Not found") + if str_out != "Not found": + start_index = output.index(start_string) + 1 + stop_index = output.index(stop_string) + strings_out = output[start_index:stop_index] + else: + strings_out.append(str_out) + + return strings_out + + +def get_espressif_flash_status(): + output = run_espressif_tool("read_flash_status") + + string_out = next( + (s for s in output.split("\n") if "Status value" in s), "Not found" + ) + if string_out != "Not found": + string_out = string_out.split("Status value: ")[-1] + return string_out + + +def get_espressif_mac_address(): + output = run_espressif_tool("read_mac") + + string_out = next((s for s in output.split("\n") if "MAC" in s), "Not found") + if string_out != "Not found": + string_out = string_out.split("MAC: ")[-1] + return string_out + + +def get_espressif_bootloader_version(bindir): + """ + Get the bootloader version for Espressif chips from the bootloader binary. This + function works on Linux, Windows, and macOS. + + Args: + bindir (str): The path to the bootloader binary directory. + + Returns: + dict: A dictionary containing the bootloader version for each chip. + """ + + regex = r"^(?=.*\bv\d+(\.\d+){1,2}\b).+$" + bootloader_chips = [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c2", + "esp32c3", + "esp32c6", + "esp32h2", + ] + bootloader_version = {} + + for chip in bootloader_chips: + file = "bootloader-{}.bin".format(chip) + path = os.path.join(bindir, file) + + if os.path.isfile(path): + if platform.system() == "Linux": + process = subprocess.Popen(["strings", path], stdout=subprocess.PIPE) + elif platform.system() == "Windows": + process = subprocess.Popen( + [ + "powershell", + "Get-Content -Raw -Encoding Byte {} |".format(path) + + " % { [char[]]$_ -join \"\" } | Select-String -Pattern '[\\x20-\\x7E]+'" + + " -AllMatches | % { $_.Matches } | % { $_.Value }", + ], + stdout=subprocess.PIPE, + ) + elif platform.system() == "Darwin": + process = subprocess.Popen( + ["strings", "-", path], stdout=subprocess.PIPE + ) + else: + bootloader_version[chip] = "Not supported on host OS" + break + + output, error = process.communicate() + strings_out = output.decode("utf-8", errors="ignore") + matches = re.finditer(regex, strings_out, re.MULTILINE) + + try: + bootloader_version[chip] = next(matches).group(0) + except StopIteration: + bootloader_version[chip] = "Unknown" + + else: + bootloader_version[chip] = "Bootloader image not found" + + return bootloader_version + + +def get_espressif_toolchain_version(): + """ + Get the version of different toolchains used by Espressif chips. + + Args: + None. + + Returns: + dict: A dictionary containing the toolchain version for each toolchain. + """ + + toolchain_version = {} + toolchain_bins = [ + "clang", + "gcc", + "xtensa-esp32-elf-gcc", + "xtensa-esp32s2-elf-gcc", + "xtensa-esp32s3-elf-gcc", + "riscv32-esp-elf-gcc", + "riscv64-unknown-elf-gcc", + ] + + for binary in toolchain_bins: + try: + version_output = subprocess.check_output( + [binary, "--version"], stderr=subprocess.STDOUT, universal_newlines=True + ) + version_lines = version_output.split("\n") + version = version_lines[0].strip() + toolchain_version[binary] = version + except (subprocess.CalledProcessError, FileNotFoundError): + toolchain_version[binary] = "Not found" + + return toolchain_version + + +def get_espressif_hal_version(hal_dir): + """ + Get the version of the ESP HAL used by Espressif chips. + + Args: + None. + + Returns: + str: The ESP HAL version. + """ + + hal_version = "Not found" + + try: + if os.path.isdir(os.path.join(hal_dir, ".git")): + hal_version_output = subprocess.check_output( + ["git", "describe", "--tags", "--always"], + cwd=hal_dir, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + hal_version = hal_version_output.strip() + except (subprocess.CalledProcessError, FileNotFoundError): + pass + + return hal_version + + +# Common functions # + + +def verbose(info, logs): + """ + Prepare and print information on build screen. + + Args: + info (dict): Information dictionary of build system. + logs (str): Vendor specific information string. + + Returns: + None. + """ + + if args.verbose: + + out_str = "\n" + "=" * 79 + "\n" + + out_str += "NuttX RTOS info: {}\n\n".format(info["OS_VERSION"]) + if args.flags: + out_str += "NuttX CFLAGS: {}\n\n".format(info["NUTTX_CFLAGS"]) + out_str += "NuttX CXXFLAGS: {}\n\n".format(info["NUTTX_CXXFLAGS"]) + out_str += "NuttX LDFLAGS: {}\n\n".format(info["NUTTX_LDFLAGS"]) + + if args.config: + out_str += "NuttX configuration options: \n" + for i in range(len(info["NUTTX_CONFIG"])): + out_str += " " + info["NUTTX_CONFIG"][i].replace('"', '\\"') + "\n" + out_str += "\n\n" + + if args.path: + out_str += "Host system PATH: \n" + for i in range(len(info["SYSTEM_PATH"])): + out_str += " " + info["SYSTEM_PATH"][i] + "\n" + out_str += "\n\n" + + if args.packages: + out_str += "Host system installed packages: \n" + for i in range(len(info["INSTALLED_PACKAGES"])): + out_str += " " + info["INSTALLED_PACKAGES"][i] + "\n" + out_str += "\n\n" + + if args.modules: + out_str += "Host system installed python modules: \n" + for i in range(len(info["PYTHON_MODULES"])): + out_str += " " + info["PYTHON_MODULES"][i] + "\n" + out_str += "\n\n" + + if args.espressif: + out_str += "Espressif specific information: \n\n" + out_str += logs + out_str += "=" * 79 + "\n" + + eprint(out_str) + + +def eprint(*args, **kwargs): + """ + Prints the arguments passed to stderr. + """ + + print(*args, file=sys.stderr, **kwargs) + + +def get_installed_packages(): + """ + Gets the list of packages installed on the host system. This function works on + Linux (Debian, Red Hat, and Arch-based distros), Windows, and macOS. + + Args: + None. + + Returns: + list: Packages (with version) installed on the host system. + """ + + packages = [] + + if platform.system() == "Linux": + package_managers = [ + { + "name": "Dpkg", + "command": ["dpkg", "-l"], + "skip_lines": 5, + "info_name": 1, + "info_version": 2, + }, + { + "name": "Rpm", + "command": ["rpm", "-qa", "--queryformat", '"%{NAME} %{VERSION}\\n"'], + "skip_lines": 0, + "info_name": 0, + "info_version": 1, + }, + { + "name": "Pacman", + "command": ["pacman", "-Q", "--queryformat", '"%n %v\\n"'], + "skip_lines": 0, + "info_name": 0, + "info_version": 1, + }, + ] + + for manager in package_managers: + try: + process = subprocess.Popen(manager["command"], stdout=subprocess.PIPE) + output, error = process.communicate() + lines = output.decode("utf-8").splitlines() + + # Skip the specified number of lines based on the package manager + if lines: + lines = lines[manager["skip_lines"] :] + + current_packages = [] + for line in lines: + package_info = line.split() + package = package_info[manager["info_name"]] + version = package_info[manager["info_version"]] + current_packages.append(f"{package} ({version})") + + if current_packages: + packages.extend(current_packages) + break + + except (FileNotFoundError, subprocess.CalledProcessError): + pass + + elif platform.system() == "Windows": + try: + output = subprocess.check_output( + [ + "powershell", + "Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\" + + "CurrentVersion\\Uninstall\\* | Select-Object DisplayName, DisplayVersion", + ] + ) + output = output.decode("utf-8", errors="ignore").split("\r\n") + for line in output[3:]: + line = line.strip() + if line: + match = re.match(r"^(.*?)(\s{2,}[^ ]+)?$", line) + if match: + name = match.group(1).strip() + version = ( + match.group(2).strip() if match.group(2) else "Unknown" + ) + packages.append(f"{name} ({version})") + except subprocess.CalledProcessError: + eprint("Error: Failed to get installed packages.") + + elif platform.system() == "Darwin": + try: + output = subprocess.check_output(["pkgutil", "--pkgs"]) + output = output.decode("utf-8").split("\n") + for package in output: + if "." in package: + try: + info = subprocess.check_output( + ["pkgutil", "--pkg-info", package] + ) + info = info.decode("utf-8") + version = info.split("version: ")[1].split("\n")[0] + except subprocess.CalledProcessError: + version = "Unknown" + packages.append(f"{package} ({version})") + except subprocess.CalledProcessError: + eprint("Error: Failed to get installed packages.") + + packages.sort() + return packages + + +def get_python_modules(): + """ + Gets the list of python modules installed on the host system. + + Args: + None. + + Returns: + list: Python modules (with version) installed on the host system. + """ + + modules = [] + + output = subprocess.check_output( + ["pip", "list", "--format=freeze"], universal_newlines=True + ) + for line in output.splitlines(): + if line.startswith("#"): + continue + package_info = line.split("==") + if len(package_info) > 1: + modules.append("{}-{}".format(package_info[0], package_info[1])) + else: + modules.append(package_info[0]) + return modules + + +def get_system_path(): + """ + Gets the PATH environment variable. + + Args: + None. + + Returns: + str: The PATH environment variable. + """ + + return os.environ.get("PATH", "") + + +def get_os_version(): + """ + Gets the OS distribution and version. This function works on Linux, Windows, + and macOS. On Linux, if the distro package is installed, it will be used to + get the OS distribution. + + Args: + None. + + Returns: + str: The OS distribution and version. + """ + + os_name = platform.system() + sys_id = "" + + if os_name == "Windows": + os_distro = "Windows" + os_version = platform.win32_ver()[0] + sys_id = f"{os_distro} {os_version}" + + elif os_name == "Darwin": + os_distro = "macOS" + os_uname = " ".join(platform.uname()) + os_version = platform.mac_ver()[0] + sys_id = f"{os_distro} {os_version} {os_uname}" + + elif os_name == "Linux": + os_uname = " ".join(platform.uname()) + try: + import distro + + sys_id = distro.name(pretty=True) + " " + os_uname + except ImportError: + sys_id = os_uname + + return sys_id + + +def get_compilation_flags(flags): + """ + Gets the compilation flags used to compile the NuttX source code and splits + them into a list. + + Args: + arg (str): The compilation flags. + + Returns: + list: The compilation flags. + """ + + if not flags: + return [""] + + flag_list = flags.split(" -") + flag_list[0] = flag_list[0][1:] + flag_list = ["-" + flag for flag in flag_list] + + return flag_list + + +def generate_header(args): + """ + Generates the sysinfo.h header file that contains information about the host system + and NuttX configuration that can be used by NuttX applications. + + Args: + args (argparse.Namespace): The command line arguments. + + Returns: + str: The contents of the sysinfo.h header file. + """ + + info = {} + output = "" + build_output = "" + + output += "/****************************************************************************\n" + output += " * sysinfo.h\n" + output += " *\n" + output += " * Auto-generated by a Python script. Do not edit!\n" + output += " *\n" + output += ( + " * This file contains information about the host and target systems that\n" + ) + output += " * can be used by NuttX applications.\n" + output += " *\n" + output += " ****************************************************************************/\n\n" + + output += "#ifndef __SYSTEM_INFO_H\n" + output += "#define __SYSTEM_INFO_H\n\n" + + # NuttX Compilation Flags + + if args.flags: + cflags, cxxflags, ldflags = args.flags + + if cflags: + cflags = cflags[1:-1] + if cxxflags: + cxxflags = cxxflags[1:-1] + if ldflags: + ldflags = ldflags[1:-1] + + info["NUTTX_CFLAGS"] = get_compilation_flags(cflags) + info["NUTTX_CXXFLAGS"] = get_compilation_flags(cxxflags) + info["NUTTX_LDFLAGS"] = get_compilation_flags(ldflags) + + output += "#define NUTTX_CFLAGS_ARRAY_SIZE {}\n".format( + len(info["NUTTX_CFLAGS"]) + ) + output += "static const char *NUTTX_CFLAGS[NUTTX_CFLAGS_ARRAY_SIZE] =\n{\n" + for i in range(len(info["NUTTX_CFLAGS"])): + output += ' "' + info["NUTTX_CFLAGS"][i] + '",\n' + output += "};\n\n" + + output += "#define NUTTX_CXXFLAGS_ARRAY_SIZE {}\n".format( + len(info["NUTTX_CXXFLAGS"]) + ) + output += "static const char *NUTTX_CXXFLAGS[NUTTX_CXXFLAGS_ARRAY_SIZE] =\n{\n" + for i in range(len(info["NUTTX_CXXFLAGS"])): + output += ' "' + info["NUTTX_CXXFLAGS"][i] + '",\n' + output += "};\n\n" + + output += "#define NUTTX_LDFLAGS_ARRAY_SIZE {}\n".format( + len(info["NUTTX_LDFLAGS"]) + ) + output += "static const char *NUTTX_LDFLAGS[NUTTX_LDFLAGS_ARRAY_SIZE] =\n{\n" + for i in range(len(info["NUTTX_LDFLAGS"])): + output += ' "' + info["NUTTX_LDFLAGS"][i] + '",\n' + output += "};\n\n" + + # NuttX Configuration + + if args.config: + info["NUTTX_CONFIG"] = [] + config_path = os.path.abspath("./.config") + try: + with open(config_path, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + info["NUTTX_CONFIG"].append(line) + + output += "#define NUTTX_CONFIG_ARRAY_SIZE {}\n".format( + len(info["NUTTX_CONFIG"]) + ) + output += "static const char *NUTTX_CONFIG[NUTTX_CONFIG_ARRAY_SIZE] =\n{\n" + for i in range(len(info["NUTTX_CONFIG"])): + output += ' "' + info["NUTTX_CONFIG"][i].replace('"', '\\"') + '",\n' + output += "};\n\n" + except FileNotFoundError: + print("Error: NuttX configuration file not found: {}".format(config_path)) + sys.exit(1) + + # OS Version + + info["OS_VERSION"] = get_os_version() + output += 'static const char OS_VERSION[] = "{}";\n\n'.format(info["OS_VERSION"]) + + # System Path + + if args.path: + info["SYSTEM_PATH"] = str(get_system_path()).split(":") + output += "#define SYSTEM_PATH_ARRAY_SIZE {}\n".format(len(info["SYSTEM_PATH"])) + output += "static const char *SYSTEM_PATH[SYSTEM_PATH_ARRAY_SIZE] =\n{\n" + for i in range(len(info["SYSTEM_PATH"])): + output += ' "' + info["SYSTEM_PATH"][i] + '",\n' + output += "};\n\n" + + # Installed Packages + + if args.packages: + info["INSTALLED_PACKAGES"] = [str(x) for x in get_installed_packages()] + output += "#define INSTALLED_PACKAGES_ARRAY_SIZE {}\n".format( + len(info["INSTALLED_PACKAGES"]) + ) + output += "static const char *INSTALLED_PACKAGES[INSTALLED_PACKAGES_ARRAY_SIZE] =\n{\n" + for i in range(len(info["INSTALLED_PACKAGES"])): + output += ' "' + info["INSTALLED_PACKAGES"][i] + '",\n' + output += "};\n\n" + + # Python Modules + + if args.modules: + info["PYTHON_MODULES"] = [str(x) for x in get_python_modules()] + output += "#define PYTHON_MODULES_ARRAY_SIZE {}\n".format( + len(info["PYTHON_MODULES"]) + ) + output += "static const char *PYTHON_MODULES[PYTHON_MODULES_ARRAY_SIZE] =\n{\n" + for i in range(len(info["PYTHON_MODULES"])): + output += ' "' + info["PYTHON_MODULES"][i] + '",\n' + output += "};\n\n" + + # Vendor Specific + + # Espressif + + if args.espressif: + # Espressif bootloader version + + info["ESPRESSIF_BOOTLOADER"] = get_espressif_bootloader_version( + args.espressif[0] + ) + output += "#define ESPRESSIF_BOOTLOADER_ARRAY_SIZE {}\n".format( + len(info["ESPRESSIF_BOOTLOADER"]) + ) + output += "static const char *ESPRESSIF_BOOTLOADER[ESPRESSIF_BOOTLOADER_ARRAY_SIZE] =\n{\n" + build_output = "Bootloader version:\n" + for key, value in info["ESPRESSIF_BOOTLOADER"].items(): + output += ' "{}: {}",\n'.format(key, value) + build_output += " {}: {},\n".format(key, value) + output += "};\n\n" + build_output += "\n\n" + + # Espressif toolchain version + + info["ESPRESSIF_TOOLCHAIN"] = get_espressif_toolchain_version() + output += "#define ESPRESSIF_TOOLCHAIN_ARRAY_SIZE {}\n".format( + len(info["ESPRESSIF_TOOLCHAIN"]) + ) + output += "static const char *ESPRESSIF_TOOLCHAIN[ESPRESSIF_TOOLCHAIN_ARRAY_SIZE] =\n{\n" + build_output += "Toolchain version:\n" + for key, value in info["ESPRESSIF_TOOLCHAIN"].items(): + output += ' "{}: {}",\n'.format(key, value) + build_output += " {}: {},\n".format(key, value) + output += "};\n\n" + build_output += "\n\n" + + # Espressif esptool version + + info["ESPRESSIF_ESPTOOL"] = next( + (s for s in get_python_modules() if "esptool" in s), "Not found" + ) + output += 'static const char ESPRESSIF_ESPTOOL[] = "{}";\n\n'.format( + info["ESPRESSIF_ESPTOOL"].split("-")[1] + ) + build_output += "Esptool version: {}\n\n".format( + info["ESPRESSIF_ESPTOOL"].split("-")[1] + ) + + # Espressif HAL version + + info["ESPRESSIF_HAL"] = get_espressif_hal_version(args.espressif[1]) + output += 'static const char ESPRESSIF_HAL[] = "{}";\n\n'.format( + info["ESPRESSIF_HAL"] + ) + build_output += "HAL version: {}\n\n".format(info["ESPRESSIF_HAL"]) + + if args.espressif_chip and info["ESPRESSIF_ESPTOOL"] not in "Not found": + info["ESPRESSIF_CHIP_ID"] = get_espressif_chip_id() + output += 'static const char ESPRESSIF_CHIP_ID[] = "{}";\n\n'.format( + info["ESPRESSIF_CHIP_ID"] + ) + build_output += "CHIP ID: = {}\n\n".format(info["ESPRESSIF_CHIP_ID"]) + + info["ESPRESSIF_FLASH_ID"] = get_espressif_flash_id() + output += "#define ESPRESSIF_FLASH_ID_ARRAY_SIZE {}\n".format( + len(info["ESPRESSIF_FLASH_ID"]) + ) + output += "static const char *ESPRESSIF_FLASH_ID[ESPRESSIF_FLASH_ID_ARRAY_SIZE] =\n{\n" + build_output += "Flash ID:\n" + for each_item in info["ESPRESSIF_FLASH_ID"]: + output += ' "{}",\n'.format(each_item) + build_output += " {}\n".format(each_item) + output += "};\n\n" + build_output += "\n\n" + + info["ESPRESSIF_SECURITY_INFO"] = get_espressif_security_info() + output += "#define ESPRESSIF_SECURITY_INFO_ARRAY_SIZE {}\n".format( + len(info["ESPRESSIF_SECURITY_INFO"]) + ) + output += "static const char *ESPRESSIF_SECURITY_INFO[ESPRESSIF_SECURITY_INFO_ARRAY_SIZE] =\n{\n" + build_output += "Security information: \n" + for each_item in info["ESPRESSIF_SECURITY_INFO"]: + output += ' "{}",\n'.format(each_item) + build_output += " {}\n".format(each_item) + output += "};\n\n" + build_output += "\n\n" + + info["ESPRESSIF_FLASH_STAT"] = get_espressif_flash_status() + output += 'static const char ESPRESSIF_FLASH_STAT[] = "{}";\n\n'.format( + info["ESPRESSIF_FLASH_STAT"] + ) + build_output += "Flash status: {}\n\n".format(info["ESPRESSIF_FLASH_STAT"]) + + info["ESPRESSIF_MAC_ADDR"] = get_espressif_mac_address() + output += 'static const char ESPRESSIF_MAC_ADDR[] = "{}";\n\n'.format( + info["ESPRESSIF_MAC_ADDR"] + ) + build_output += "MAC address: {}\n\n".format(info["ESPRESSIF_MAC_ADDR"]) + + elif args.espressif_chip_runtime: + output += "#define ESPRESSIF_INFO_SIZE 384\n" + output += "static char ESPRESSIF_INFO[ESPRESSIF_TOOLCHAIN_ARRAY_SIZE] =\n{\n 0\n};\n\n" + + verbose(info, build_output) + + output += "#endif /* __SYSTEM_INFO_H */\n" + + return output + + +# Main # + +if __name__ == "__main__": + """ + Main function for the script. This function is called when the script is + executed directly. It parses the command line arguments and calls the + appropriate functions. It also prints the output generated to stdout. + + Required arguments: + nuttx_path: The path to the NuttX source directory. + + Optional arguments: + The command line arguments. The available arguments are: + -h, --help: + Show the help message and exit. + -m, --modules: + Get the list of installed Python modules. + -k, --packages: + Get the list of installed system packages. + -p, --path: + Get the system PATH environment variable. + -c, --config: + Get the NuttX compilation configurations used. + -f, --flags : + Provide the NuttX compilation flags used. + --verbose: + Enable printing information during build. + --espressif : + Get Espressif specific information. + Requires the path to the bootloader binary directory. + --espressif_chip: + Get Espressif specific information about chip. + Requires device connection during build. + --espressif_chip_runtime: + Get Espressif specific information about chip + on runtime. + Requires device connection during build. + """ + + # Generic arguments + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("nuttx_path", help="NuttX source directory path.") + parser.add_argument( + "-h", + "--help", + action="help", + default=argparse.SUPPRESS, + help="Show this help message and exit.", + ) + parser.add_argument( + "-m", + "--modules", + action="store_true", + help="Get the list of installed Python modules.", + ) + parser.add_argument( + "-k", + "--packages", + action="store_true", + help="Get the list of installed system packages.", + ) + parser.add_argument( + "-p", + "--path", + action="store_true", + help="Get the system PATH environment variable.", + ) + parser.add_argument( + "-c", + "--config", + action="store_true", + help="Get the NuttX compilation configurations used.", + ) + parser.add_argument( + "-f", + "--flags", + nargs=3, + default=[], + metavar=("CFLAGS", "CXXFLAGS", "LDFLAGS"), + action=validate_flags_arg, + help="Provide the NuttX compilation and linker flags used.", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Enable printing information during build", + ) + + # Vendor specific arguments + + parser.add_argument( + "--espressif", + nargs=2, + default=[], + metavar=("ESPTOOL_BINDIR", "ESP_HAL_DIR"), + help="Get Espressif specific information. Requires the path to the bootloader binary and ESP HAL directories.", + ) + + parser.add_argument( + "--espressif_chip", + action="store_true", + help="Get Espressif specific information about chip. Requires device connection during build.", + ) + + parser.add_argument( + "--espressif_chip_runtime", + action="store_true", + help="Get Espressif specific information about chip on runtime. Requires device connection during build.", + ) + + # Parse arguments + + if len(sys.argv) == 1: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + os.chdir(args.nuttx_path) + header = generate_header(args) + print(header) diff --git a/tools/parse_sysinfo.py b/tools/parse_sysinfo.py new file mode 100644 index 0000000000000..60abfa66d2bf6 --- /dev/null +++ b/tools/parse_sysinfo.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# tools/parse_sysinfo.py +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import argparse +import os +import re +import sys + + +def parse_information_from_header(file_path): + """ + Parses the file that contains information about the host system + and NuttX configuration(sysinfo.h). + + Args: + file_path (str): Path of the file to parse. + + Returns: + dict: The contents parsed from file_path (sysinfo.h) header file. + """ + + VARIABLE_NAMES_REGEX = r"static\s+const\s+char\s+\**([A-Za-z0-9_]+)\s*" + VARIABLE_VALUES_REGEX = r'"([^"]*)"|{([^}]+)};' + result = {} + var_name_to_print_dict = { + "NUTTX_CFLAGS": "NuttX CFLAGS", + "NUTTX_CXXFLAGS": "NuttX CXXFLAGS", + "NUTTX_LDFLAGS": "NuttX LDFLAGS", + "NUTTX_CONFIG": "NuttX configuration options", + "SYSTEM_PATH": "Host system PATH", + "OS_VERSION": "Host system OS", + "INSTALLED_PACKAGES": "Host system installed packages", + "PYTHON_MODULES": "Host system installed python modules", + "ESPRESSIF_BOOTLOADER": "Espressif specific information:\n\nToolchain version", + "ESPRESSIF_TOOLCHAIN": "Toolchain version", + "ESPRESSIF_ESPTOOL": "Esptool version", + "ESPRESSIF_HAL": "HAL version", + "ESPRESSIF_CHIP_ID": "CHIP ID", + "ESPRESSIF_FLASH_ID": "Flash ID", + "ESPRESSIF_SECURITY_INFO": "Security information", + "ESPRESSIF_FLASH_STAT": "Flash status", + "ESPRESSIF_MAC_ADDR": "MAC address", + } + + # Regular expression pattern to match array definition + + keys_pattern = re.compile(VARIABLE_NAMES_REGEX, re.DOTALL) + values_pattern = re.compile(VARIABLE_VALUES_REGEX, re.DOTALL) + + with open(file_path, "r") as file: + content = file.read() + + # Match array definition using the regex + + keys_array = keys_pattern.findall(content) + values_array = values_pattern.findall(content) + + # Process values to print it prettier + + for i in range(len(values_array)): + tmp_list = [] + for y in range(len(values_array[i])): + tmp_str = values_array[i][y] + tmp_str = tmp_str.replace('"', "") + tmp_str = tmp_str.replace("\n ", "", 1) + tmp_str = tmp_str.replace(",", "") + + if tmp_str != "": + tmp_list.append(tmp_str) + + values_array[i] = tuple(tmp_list) + + keys_values_to_return = [var_name_to_print_dict[x] for x in keys_array] + result = dict(zip(keys_values_to_return, values_array)) + + return result + + +# Main # + + +if __name__ == "__main__": + """ + Main function for the script. This function is called when the script is + executed directly. It prints the output generated to stdout. + + Required arguments: + nuttx_path: The path to the NuttX source directory. + + Optional arguments: + The command line arguments. The available arguments are: + -h, --help: + Show the help message and exit. + -f, --file : + Provide the diagnostic file output(sysinfo.h). + """ + + # Generic arguments + + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("nuttx_path", help="NuttX source directory path.") + parser.add_argument( + "-h", + "--help", + action="help", + default=argparse.SUPPRESS, + help="Show this help message and exit.", + ) + parser.add_argument( + "-f", + "--file", + default="", + metavar=("SYSINFO_FILE"), + help="Provide the diagnostic file output(sysinfo.h).", + ) + # Parse arguments + + if len(sys.argv) == 1: + parser.print_help() + sys.exit(1) + + args = parser.parse_args() + os.chdir(args.nuttx_path) + + parsed_data = parse_information_from_header(args.file) + + # Print the extracted name and values + + if parsed_data: + for each_key in parsed_data.keys(): + print("{}:".format(each_key)) + for each_value in parsed_data[each_key]: + print(" {}".format(each_value)) + print("") + else: + print("No matching array found.")