diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 7980ee5f..8014e8a3 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -281,3 +281,5 @@ workdir www wxgui xml +version-check +0x48piraj diff --git a/src/fprime/util/build_helper.py b/src/fprime/util/build_helper.py index 34e06b46..0893e7d0 100644 --- a/src/fprime/util/build_helper.py +++ b/src/fprime/util/build_helper.py @@ -19,7 +19,7 @@ from fprime.fbuild.cli import get_target from fprime.fbuild.target import NoSuchTargetException -from .versioning import VersionException, get_version +from .versioning import VersionException, get_version, FPRIME_PIP_PACKAGES # Attempt to get pkg_resources from "setuptools" try: @@ -68,22 +68,7 @@ def validate_tools_from_requirements(build: Build): return # Now check each required tool for fprime - tools = [ - "fprime-tools", - "fprime-gds", - "fprime-fpp-to-xml", - "fprime-fpp-to-json", - "fprime-fpp-to-cpp", - "fprime-fpp-syntax", - "fprime-fpp-locate-uses", - "fprime-fpp-locate-defs", - "fprime-fpp-from-xml", - "fprime-fpp-format", - "fprime-fpp-filenames", - "fprime-fpp-depend", - "fprime-fpp-check", - ] - for tool in tools: + for tool in FPRIME_PIP_PACKAGES: for possible in possibilities: try: package_version_check(tool, possible) diff --git a/src/fprime/util/cli.py b/src/fprime/util/cli.py index bf7a4ed6..02b05d41 100644 --- a/src/fprime/util/cli.py +++ b/src/fprime/util/cli.py @@ -16,7 +16,13 @@ from fprime.fbuild.target import Target from fprime.fpp.cli import add_fpp_parsers from fprime.util.build_helper import load_build -from fprime.util.commands import run_code_format, run_hash_to_file, run_info, run_new +from fprime.util.commands import ( + run_code_format, + run_hash_to_file, + run_info, + run_new, + run_version_check, +) from fprime.util.help_text import HelpText from fprime.fpp.visualize import add_fpp_viz_parsers from fprime.fpp.impl import add_fpp_impl_parsers @@ -56,6 +62,8 @@ def skip_build_loading(parsed): """ if parsed.command == "new" and parsed.new_project: return True + if parsed.command == "version-check": + return True return False @@ -111,6 +119,15 @@ def add_special_parsers( formatter_class=argparse.RawDescriptionHelpFormatter, ) + subparsers.add_parser( + "version-check", + description=help_text.long("version-check"), + help=help_text.short("version-check"), + parents=[common], + add_help=False, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + # New functionality new_parser = subparsers.add_parser( "new", @@ -211,6 +228,7 @@ def add_special_parsers( return { "hash-to-file": run_hash_to_file, "info": run_info, + "version-check": run_version_check, "new": run_new, "format": run_code_format, } diff --git a/src/fprime/util/commands.py b/src/fprime/util/commands.py index 23b1a830..68cdcb44 100644 --- a/src/fprime/util/commands.py +++ b/src/fprime/util/commands.py @@ -6,6 +6,7 @@ - hash-to-file: Processes hash-to-file to locate file - new: Creates a new component, deployment, or project - format: Formats code using clang-format + - version-check: Print out versions to help debugging @author thomas-bc """ @@ -17,6 +18,7 @@ from fprime.fbuild.builder import Build, InvalidBuildCacheException from fprime.util.code_formatter import ClangFormatter +from .versioning import VersionException, FPRIME_PIP_PACKAGES from fprime.util.cookiecutter_wrapper import ( new_component, new_deployment, @@ -182,3 +184,53 @@ def run_code_format( for filename in parsed.files: clang_formatter.stage_file(Path(filename)) return clang_formatter.execute(build, parsed.path, ({}, parsed.pass_through)) + + +def run_version_check( + base: Build, parsed: argparse.Namespace, _: Dict[str, str], __: Dict[str, str], ___ +): + """Print out versions to help debugging""" + + try: + import platform + + print(f"Python version: {platform.python_version()}") + print(f"Operating System: {platform.system()}") + print(f"CPU Architecture: {platform.machine()}") + print(f"Platform: {platform.platform()}") + except ImportError: # Python >=3.6 + print("[WARNING] Cannot import 'platform'.") + + try: + import subprocess + + cmake_version = ( + subprocess.check_output(["cmake", "--version"]) + .decode("utf-8") + .splitlines()[0] + .split()[2] + ) + print(f"CMake version: {cmake_version}") + except ImportError: # Python >=3.6 + print("[WARNING] Cannot import 'subprocess'.") + + try: + import pip + + print(f"Pip version: {pip.__version__}") + except ModuleNotFoundError: # Python >=3.6 + print("[WARNING] Cannot import 'Pip'.") + + try: + import pkg_resources + except ModuleNotFoundError: # Python >=3.6 + print("[WARNING] Cannot import 'pkg_resources'. Will not check tool versions.") + return + + print("Pip packages:") + for tool in FPRIME_PIP_PACKAGES: + try: + version = pkg_resources.get_distribution(tool).version + print(f" {tool}=={version}") + except (OSError, VersionException) as exc: + print(f"[WARNING] {exc}") diff --git a/src/fprime/util/help_text.py b/src/fprime/util/help_text.py index 5b66098d..391826f3 100644 --- a/src/fprime/util/help_text.py +++ b/src/fprime/util/help_text.py @@ -241,7 +241,7 @@ """, "info": f"""Print contextual target and build cache information before exiting -'{EXECUTABLE} info' is used to print contextual information to the user before exiting. It will print the available] +'{EXECUTABLE} info' is used to print contextual information to the user before exiting. It will print the available commands within the current context (working directory, '-p/--path', '-r/--root', etc.) and then exit. Users may use the info command as a way to test and understand how {EXECUTABLE} is mapping to the context and targets used. info may also be used to locate the artifact output folders within the build cache in order to see generated files, compiler @@ -249,6 +249,14 @@ '{EXECUTABLE} info' will print information for both normal and unit testing builds when possible. If '--build-cache' is specified then only the information for that build cache will be printed. +""", + "version-check": f"""Print out toolchain versions to help debugging + +'{EXECUTABLE} version-check' is used to display information about toolchain versions. It will output details such as +the installed Python version, the installed Pip version, and version information for all the necessary tools for fprime +before exiting. Users can utilize the version-check command as a tool for debugging and comprehending the dependencies for {EXECUTABLE}. + +'{EXECUTABLE} version-check' will print information about toolchain versions for debugging purposes. """, "hash-to-file": f"""Convert FW_ASSERT file id hash to file path diff --git a/src/fprime/util/versioning.py b/src/fprime/util/versioning.py index 9fd068fe..68f783c8 100644 --- a/src/fprime/util/versioning.py +++ b/src/fprime/util/versioning.py @@ -8,6 +8,23 @@ class VersionException(Exception): pass +FPRIME_PIP_PACKAGES = [ + "fprime-tools", + "fprime-gds", + "fprime-fpp-to-xml", + "fprime-fpp-to-json", + "fprime-fpp-to-cpp", + "fprime-fpp-syntax", + "fprime-fpp-locate-uses", + "fprime-fpp-locate-defs", + "fprime-fpp-from-xml", + "fprime-fpp-format", + "fprime-fpp-filenames", + "fprime-fpp-depend", + "fprime-fpp-check", +] + + def get_version(package: str, requirements: Path): """Get the version as specified in the requirements file diff --git a/test/fprime/util/commands_unit_test.py b/test/fprime/util/commands_unit_test.py new file mode 100644 index 00000000..dd68fa9f --- /dev/null +++ b/test/fprime/util/commands_unit_test.py @@ -0,0 +1,53 @@ +""" +(test) fprime-utils commands: + +Tests the F prime util commands. +@author 0x48piraj +""" + +import unittest + + +class CommandsTestCases(unittest.TestCase): + def test_build_command(self): + pass + + def test_check_command(self): + pass + + def test_generate_command(self): + pass + + def test_purge_command(self): + pass + + def test_fpp_check_command(self): + pass + + def test_fpp_to_xml_command(self): + pass + + def test_visualize_command(self): + pass + + def test_impl_command(self): + pass + + def test_hash_to_file_command(self): + pass + + def test_info_command(self): + pass + + def test_new_command(self): + pass + + def test_format_command(self): + pass + + def test_version_check_command(self): + pass + + +if __name__ == "__main__": + unittest.main()