From 6a05970a763657cbb8e25966ea790617fa747e71 Mon Sep 17 00:00:00 2001 From: Kip Hamiltons Date: Sun, 24 Nov 2024 19:44:54 +1100 Subject: [PATCH] Squashed 'nuclear/' changes from 8f900503..128972e2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 128972e2 Merge pull request #9 from KipHamiltons/kip/nubots-current e0a460cf Merge remote-tracking branch 'upstream/master' into kip/nubots-current 0ceafe62 Update format.py to try to better track renames (#1284) 3f5bf8e9 Add protobuf to the license formatting tool (#1277) e3014bec Update NUClear to latest version (#1264) 6968d16f Add MIT licence (#985) 65b64d92 Update types to double and messages to use new `iso3` (#1123) 27f5a3ac Update `./b` tools and NUClear roles (#1090) 451ec096 Move scripts from ScriptEngine to its own folder, for Director (#1028) 5521de23 Remove undefined behaviour in Protobuf to Neutron conversions (#999) 006c5501 Apply the formatting from #979 (#983) a032650e Add capability to put roles in folders (#866) a30ee3d1 Static Build (#893) 3e86084a Bump zlib and black versions (#816) e99f02b5 Autocomplete (#665) 434cfae7 Compile unused modules as part of pipeline (#633) d7589fa8 Clang-tidy - some automated fixes (#698) f697c805 Rename master->main (#711) 58679eed Fix odometry (#481) c649f6f1 Fix webots communication (#475) ed3b72d6 Adds clang-tidy to the codebase (#420) bf926d55 Clang-tidy fixes for NUClear Roles (#418) ab87944f Add in message reflection capabilities to Neutrons (#414) 3adc11f2 Modernises CMake codebase wide (#395) a7c72f7a Fixed what cmake looks for to work out if it has built the neutrons. (#385) da0278f6 Upgrade the formatting tools to the latest versions (#377) 29ba2bb4 Changes path to prefer our tools (#373) d07c8ebf Fix bug in proto_matrix.hpp where memory was not resized properly (#368) b775021f Change header files names to hpp (#342) c5aedebf Make the Dockerfile build as root and do the user creation at the end (#331) 9e6dfde9 Update NUClear Roles cmake-format configuration (#319) 74673a42 Take advantage of clang-formats new rules (#269) ff1cc105 Allow overwriting of the module command (#296) b90d6140 Remove (almost all) build warnings (#292) f981bb25 Improve the b scripts logic so that it can find nested tools (#291) 9c2422ef Remove util-linux as it is poision (makes machines unbootable) (#288) 3a1bc5d6 Fix the GitHub security warning for Pillow (#287) a4fa1057 Change how the python prefix is done to use .pth files (#284) e594d730 Upgrade nuclear roles to C++17 (#272) 13a45704 Remove Armadillo from all modules in the platform meta-module (#267) 43448c6a Upgrades to the docker system to fix bugs and improve builds (#266) 79129e54 Add docker as our build system and replace vagrant and puppet (#255) e50a6d60 Amalgamate the nbs tools into a single subcommand and add a stats tool (#262) c720132b Update NUClearRoles (#260) e7c2f797 Merge commit '2dd12eff460ee49e6e018b98d06f13f1d8c612f3' into houliston/nuclearroles 20e96168 Apply formatting to NUClearRoles 7e91fab8 Add a cmake format file, format cmake files and get buildkite to test they stay formatted (#259) bc422210 Add a cmake format file, format cmake files and get buildkite to test they stay formatted (#259) 38024db5 Improve the nbs decoder so it does not need the VM to run (#258) 6ae03c39 Improve the nbs decoder so it does not need the VM to run (#258) 9beacabf Make the b script commands know when they can't be executed (#256) 86a61672 Make the b script commands know when they can't be executed (#256) fa0ed681 Add initial support for BuildKite (#254) 1bfcf401 Add initial support for BuildKite (#254) 4edb00e4 The great purge of 2019 🤯🔫 (#243) 5a637c47 The great purge of 2019 🤯🔫 (#243) 8d4ecb15 Change training for foot down detector to be more robust and train directly from NBS files (#214) f348d876 Change training for foot down detector to be more robust and train directly from NBS files (#214) 7f5c7aaa Increase black line width to 120 (#220) 7a75009e Increase black line width to 120 (#220) 11fdb09c Blacken all python (#218) e153e38e Blacken all python (#218) 1beb27b9 Swap matrices to Column major order (#207) d9dfd7bf Swap matrices to Column major order (#207) 3b607fbb Split the nbs decoder into a utility to be used by other tools (#212) f1052d4b Split the nbs decoder into a utility to be used by other tools (#212) 3aeb18e6 Stores dynamic matrices in row-major order in proto messages (#200) d2d0df0c Stores dynamic matrices in row-major order in proto messages (#200) 3a3a000b Update Protobuf Messages for NUsight 2 Standard (#153) d0314a00 Update Protobuf Messages for NUsight 2 Standard (#153) ca1e0b09 Update NUClear roles to latest version (#154) 4f157e20 Update NUClear roles to latest version (#154) bbaf02c4 Remove NUsight header from packets (#140) 882d7047 Replaces Spinnaker library with Aravis for Igus vision (#138) c887fac0 Fix generator for vec sizes greater than 4 (#143) 81b0c396 Add new fixed size vector types up to size 16 (#142) 212684b0 Add clang-format for proto files (#139) 67875efe Change files from windows line endings to unix line endings (#57) 1e20c146 Apply style guide using clang-format (#33) fb6a0cb0 CM730 External Project Integration (#30) 8405e27d Make the system aware of which data files are used so they can be installed 0008e1a2 Removed many old redundant and non functional modules/code. If you need to reference them, checkout the commit before this one! cc6e3239 Add a script to help with choosing between platforms 19d96429 Update libraries and fixes for new versions 30b28141 Merge commit 'd8392b9ba49654150c3eeb8507cb636c2248465f' into develop f913e621 Merge commit '5e217132079158bce87f02107f0526c3250b0d93' into develop 8fbcbad4 Added fuctionality to iterate over generated message enums. 0e17fde5 Merge branch 'feature/multi-arch' into develop 8e99458e Fixed compile errors due to NUClear renaming. 406be9ac Corrected enum if-chain for string contructor. 8e091f1c Change Eigen types to not align in messages, remove some broken code paths from Neutron bb36acc3 Move Eigen alignment info into the field class e6021c15 Fix eigen alignment for vectors 514aeca2 Add Eigen alignment to neutron messages 69c87a51 Fixed ostream operator. 9e232131 Add an stream operator to the Enum class 051feaa9 Corrected equality operator generation for messages. 52910996 Added inequality operator to generated enums. cf1b361e Add equality operator to messages 384bc5c7 Fixed up protobuf map conversion and the warning about raw pointer types. dd2e1658 Generated rule of five functions for all generated messages. ed0687a2 Removed compiler treat warnings as error flag for generated message files. 1ea1a1c6 Converted some c++ based messages to proto and neutron based messages. Added some new utilities. 9c8282a3 Started conversion of c++ based messages to proto and neutron based messages. f9efd88b Merge commit '93c8364b682f75d20120ae122c2875fd18d43d4d' into feature/multi-arch 360b8fa6 Update NUClear roles Merge commit 'a7661b9ad983ca751ae8d73053c501395ae5ef49' into feature/multi-arch 268be4eb Merged in feature/ModularWalkEngine 8108fd46 Merge branch 'develop' into feature/ModularWalkEngine e6b1d041 Updated robot send code to correctly upload toolchain libraries and binaries to the robots based on their hostname. 80774ff6 Upgraded to protobuf v3 and fixed nasty protobuf hack. 7967a477 Fixed standard messages linking problems. 26eb2208 Merge commit '8489d78e2f1529e309ec7569be481a96f8adac77' into feature/multi-arch 3b211317 Merged branch with current develop modules for any changes/fixes... 50ed041f Merge branch 'feature/multi-arch' of github.com:NUbots/NUbots into feature/multi-arch bd5f9196 Further fixes to protobuf. 7c4c2752 Further fixes to protobuf. b85ec3fc Merge branch 'develop' of github.com:NUbots/NUbots into develop 93ff9bcd Permit generated message files to violate Effective-C++ error checks. fd3d1878 Permit generated message files to violate Effective-C++ error checks. 59f57832 Merge commit '80cfd09bc0bcec914d59140c8c1f5e50fe04c806' into develop db37e7e3 Added failsafe for new nuclear roles projects 59949e67 NUClear roles updated to support nutilities 960f4f4f Need to somehow get the location of GetNUtilities.cmake from within the file. Currently hacked around this by setting variable on call 7b615fa8 Added get NUtilities.cmake for configuring the utilities to build with based on project name and/or libraries found 3be356fb Merge commit '67e3256110f12b94e88758defc7f0dcf86cb5734' into develop 43aca0c9 Update to the latest version of NUClear roles Merge commit '38da9a7bacad21033146245eb5a0a46ede213632' into develop 56508f27 Upgraded b script to be compatible with Python 3. 00dbe123 Changed console log handler and the role startup to use std::cerr by default so they don't interfere with curses apps as much. c2ef66e0 Added Effective C++ compiler warnings to build commands and forced warnings to errors. Resolved all compiler errors. 3a0eb457 Darwin send can now pull new config off the robot 0959fdeb Changing the way compile options are applied 5fc99089 Re-enable warnings and fix some warnings that came up. d85d8ab4 Edited Simple Walk planner b7bf5264 Added Bezier module git-subtree-dir: nuclear git-subtree-split: 128972e2c9412c5536b835579390175cc16eb18c --- .cmake-format.py | 261 +++++ CMakeLists.txt | 141 ++- README.md | 54 +- b.py | 204 +++- cmake/Modules/FindEigen3.cmake | 20 - cmake/Modules/FindNUClear.cmake | 5 - cmake/Modules/FindPythonLibsNew.cmake | 221 ++--- cmake/Modules/Findpybind11.cmake | 6 +- cmake/Modules/GenerateNeutron.cmake | 170 ++++ cmake/Modules/HeaderLibrary.cmake | 127 +-- cmake/Modules/NUClearCompilerSettings.cmake | 25 - cmake/Modules/ToolchainLibraryFinder.cmake | 286 ++++-- cmake/Scripts/build_message_class.py | 56 ++ cmake/Scripts/build_message_reflection.py | 140 +++ cmake/Scripts/build_outer_python_binding.py | 89 ++ {message => cmake/Scripts}/generator/Enum.py | 132 ++- cmake/Scripts/generator/Field.py | 211 ++++ cmake/Scripts/generator/File.py | 178 ++++ cmake/Scripts/generator/Message.py | 644 ++++++++++++ cmake/Scripts/generator/OneOfField.py | 58 ++ cmake/Scripts/generator/OneOfType.py | 209 ++++ cmake/Scripts/generator/__init__.py | 27 + cmake/Scripts/generator/textutil.py | 39 + cmake/Scripts/repackage_message.py | 39 + dependencies.py | 111 +++ extension/CMakeLists.txt | 41 +- extension/NUClearExtension.cmake | 94 +- extension/extension.cpp | 27 + message/CMakeLists.txt | 41 +- message/Neutron.cmake | 364 ++----- message/build_message_class.py | 29 - message/build_outer_python_binding.py | 54 - message/generator/Field.py | 156 --- message/generator/File.py | 144 --- message/generator/Message.py | 553 ----------- message/generator/OneOfField.py | 33 - message/generator/OneOfType.py | 137 --- message/generator/__init__.py | 1 - message/generator/textutil.py | 13 - message/include/message/MessageBase.h | 61 -- message/include/message/MessageBase.hpp | 86 ++ .../include/message/conversion/math_types.h | 146 --- .../include/message/conversion/math_types.hpp | 182 ++++ .../message/conversion/neutron_type_map.hpp | 206 ++++ .../message/conversion/proto_conversion.hpp | 467 +++++++++ .../message/conversion/proto_neutron_map.hpp | 194 ++++ ...roto_matrix.h => proto_neutron_sfinae.hpp} | 756 +++----------- .../include/message/conversion/proto_time.h | 74 -- .../message/conversion/proto_transform.h | 68 -- message/message.cpp | 27 + message/proto/Matrix.proto | 36 +- message/proto/Neutron.proto | 24 + message/proto/Transform.proto | 67 ++ message/proto/Vector.proto | 28 +- message/repackage_message.py | 16 - module/CMakeLists.txt | 54 +- module/NUClearModule.cmake | 427 ++++---- module/python/__init__.py | 26 + module/python/nuclear.py | 210 ++-- pyproject.toml | 5 + requirements.txt | 5 +- roles/CMakeLists.txt | 164 ++- roles/NUClearRole.cmake | 111 +-- roles/banner/__init__.py | 26 + roles/banner/ampscii.py | 576 +++++------ roles/banner/bigtext.py | 33 +- roles/generate_role.py | 103 +- tests/CMakeLists.txt | 47 + tests/message/CMakeLists.txt | 57 ++ tests/message/MessageTest.proto | 200 ++++ tests/message/converter.cpp | 936 ++++++++++++++++++ tools/module.py | 175 ---- tools/module/generate.py | 179 ++++ utility/CMakeLists.txt | 41 +- utility/NUClearUtility.cmake | 74 +- utility/utility.cpp | 27 + 76 files changed, 7165 insertions(+), 3889 deletions(-) create mode 100644 .cmake-format.py delete mode 100644 cmake/Modules/FindEigen3.cmake delete mode 100644 cmake/Modules/FindNUClear.cmake create mode 100644 cmake/Modules/GenerateNeutron.cmake delete mode 100644 cmake/Modules/NUClearCompilerSettings.cmake create mode 100755 cmake/Scripts/build_message_class.py create mode 100644 cmake/Scripts/build_message_reflection.py create mode 100755 cmake/Scripts/build_outer_python_binding.py rename {message => cmake/Scripts}/generator/Enum.py (62%) mode change 100644 => 100755 create mode 100755 cmake/Scripts/generator/Field.py create mode 100755 cmake/Scripts/generator/File.py create mode 100755 cmake/Scripts/generator/Message.py create mode 100755 cmake/Scripts/generator/OneOfField.py create mode 100755 cmake/Scripts/generator/OneOfType.py create mode 100755 cmake/Scripts/generator/__init__.py create mode 100755 cmake/Scripts/generator/textutil.py create mode 100755 cmake/Scripts/repackage_message.py create mode 100644 dependencies.py delete mode 100644 message/build_message_class.py delete mode 100644 message/build_outer_python_binding.py delete mode 100644 message/generator/Field.py delete mode 100644 message/generator/File.py delete mode 100644 message/generator/Message.py delete mode 100644 message/generator/OneOfField.py delete mode 100644 message/generator/OneOfType.py delete mode 100644 message/generator/__init__.py delete mode 100644 message/generator/textutil.py delete mode 100644 message/include/message/MessageBase.h create mode 100644 message/include/message/MessageBase.hpp delete mode 100644 message/include/message/conversion/math_types.h create mode 100644 message/include/message/conversion/math_types.hpp create mode 100644 message/include/message/conversion/neutron_type_map.hpp create mode 100644 message/include/message/conversion/proto_conversion.hpp create mode 100644 message/include/message/conversion/proto_neutron_map.hpp rename message/include/message/conversion/{proto_matrix.h => proto_neutron_sfinae.hpp} (59%) delete mode 100644 message/include/message/conversion/proto_time.h delete mode 100644 message/include/message/conversion/proto_transform.h create mode 100644 message/proto/Transform.proto delete mode 100644 message/repackage_message.py mode change 100644 => 100755 module/python/__init__.py mode change 100644 => 100755 module/python/nuclear.py create mode 100644 pyproject.toml create mode 100644 tests/CMakeLists.txt create mode 100644 tests/message/CMakeLists.txt create mode 100644 tests/message/MessageTest.proto create mode 100644 tests/message/converter.cpp delete mode 100644 tools/module.py create mode 100644 tools/module/generate.py diff --git a/.cmake-format.py b/.cmake-format.py new file mode 100644 index 0000000..6a414d7 --- /dev/null +++ b/.cmake-format.py @@ -0,0 +1,261 @@ +# +# MIT License +# +# Copyright (c) 2019 NUbots +# +# This file is part of the NUbots codebase. +# See https://github.com/NUbots/NUbots for further info. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# ---------------------------------- +# Options affecting listfile parsing +# ---------------------------------- +with section("parse"): + + # Specify structure for custom cmake functions + additional_commands = { + "HeaderLibrary": {"kwargs": {"NAME": "*", "HEADER": "*", "PATH_SUFFIX": "*", "URL": "*"}}, + "ToolchainLibraryFinder": { + "kwargs": { + "NAME": "*", + "HEADER": "*", + "LIBRARY": "*", + "PATH_SUFFIX": "*", + "BINARY": "*", + "VERSION_FILE": "*", + "VERSION_BINARY_ARGUMENTS": "*", + "VERSION_REGEX": "*", + } + }, + "nuclear_module": { + "kwargs": {"LANGUAGE": "1", "INCLUDES": "*", "LIBRARIES": "*", "SOURCES": "*", "DATA_FILES": "*"} + }, + "GenerateNeutron": {"kwargs": {"PROTO": "1", "PARENT_DIR": "1", "BUILTIN_DIR": "1", "BUILTIN_OUTPUT_DIR": "1"}}, + } + + # Specify variable tags. + vartags = [] + + # Specify property tags. + proptags = [] + +# ----------------------------- +# Options affecting formatting. +# ----------------------------- +with section("format"): + + # How wide to allow formatted cmake files + line_width = 120 + + # How many spaces to tab for indent + tab_size = 2 + + # If an argument group contains more than this many sub-groups (parg or kwarg + # groups) then force it to a vertical layout. + max_subgroups_hwrap = 2 + + # If a positional argument group contains more than this many arguments, then + # force it to a vertical layout. + max_pargs_hwrap = 6 + + # If a cmdline positional group consumes more than this many lines without + # nesting, then invalidate the layout (and nest) + max_rows_cmdline = 2 + + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False + + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False + + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = True + + # If the trailing parenthesis must be 'dangled' on its on line, then align it + # to this reference: `prefix`: the start of the statement, `prefix-indent`: + # the start of the statement, plus one indentation level, `child`: align to + # the column of the arguments + dangle_align = "prefix" + + # If the statement spelling length (including space and parenthesis) is + # smaller than this amount, then force reject nested layouts. + min_prefix_chars = 4 + + # If the statement spelling length (including space and parenthesis) is larger + # than the tab width by more than this amount, then force reject un-nested + # layouts. + max_prefix_chars = 10 + + # If a candidate layout is wrapped horizontally but it exceeds this many + # lines, then reject the layout. + max_lines_hwrap = 2 + + # What style line endings to use in the output. + line_ending = "unix" + + # Format command names consistently as 'lower' or 'upper' case + command_case = "canonical" + + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = "upper" + + # A list of command names which should always be wrapped + always_wrap = ["NUCLEAR_ROLE", "nuclear_role"] + + # If true, the argument lists which are known to be sortable will be sorted + # lexicographicall + enable_sort = True + + # If true, the parsers may infer whether or not an argument list is sortable + # (without annotation). + autosort = True + + # By default, if cmake-format cannot successfully fit everything into the + # desired linewidth it will apply the last, most agressive attempt that it + # made. If this flag is True, however, cmake-format will print error, exit + # with non-zero status code, and write-out nothing + require_valid_layout = False + + # A dictionary mapping layout nodes to a list of wrap decisions. See the + # documentation for more information. + layout_passes = {} + +# ------------------------------------------------ +# Options affecting comment reflow and formatting. +# ------------------------------------------------ +with section("markup"): + + # What character to use for bulleted lists + bullet_char = "*" + + # What character to use as punctuation after numerals in an enumerated list + enum_char = "." + + # If comment markup is enabled, don't reflow the first comment block in each + # listfile. Use this to preserve formatting of your copyright/license + # statements. + first_comment_is_literal = False + + # If comment markup is enabled, don't reflow any comment block which matches + # this (regex) pattern. Default is `None` (disabled). + literal_comment_pattern = None + + # Regular expression to match preformat fences in comments default= + # ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern = "^\\s*([`~]{3}[`~]*)(.*)$" + + # Regular expression to match rulers in comments default= + # ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'`` + ruler_pattern = "^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$" + + # If a comment line matches starts with this pattern then it is explicitly a + # trailing comment for the preceeding argument. Default is '#<' + explicit_trailing_pattern = "#<" + + # If a comment line starts with at least this many consecutive hash + # characters, then don't lstrip() them off. This allows for lazy hash rulers + # where the first hash char is not separated by space + hashruler_min_length = 10 + + # If true, then insert a space between the first hash char and remaining hash + # chars in a hash ruler, and normalize its length to fill the column + canonicalize_hashrulers = True + + # enable comment markup parsing and reflow + enable_markup = True + +# ---------------------------- +# Options affecting the linter +# ---------------------------- +with section("lint"): + + # a list of lint codes to disable + disabled_codes = [] + + # regular expression pattern describing valid function names + function_pattern = "[0-9a-z_]+" + + # regular expression pattern describing valid macro names + macro_pattern = "[0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with global + # scope + global_var_pattern = "[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with global + # scope (but internal semantic) + internal_var_pattern = "_[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with local + # scope + local_var_pattern = "[0-9a-z_]+" + + # regular expression pattern describing valid names for privatedirectory + # variables + private_var_pattern = "_[0-9a-z_]+" + + # regular expression pattern describing valid names for publicdirectory + # variables + public_var_pattern = "[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for keywords used in + # functions or macros + keyword_pattern = "[0-9A-Z_]+" + + # In the heuristic for C0201, how many conditionals to match within a loop in + # before considering the loop a parser. + max_conditionals_custom_parser = 2 + + # Require at least this many newlines between statements + min_statement_spacing = 1 + + # Require no more than this many newlines between statements + max_statement_spacing = 1 + max_returns = 6 + max_branches = 12 + max_arguments = 5 + max_localvars = 15 + max_statements = 50 + +# ------------------------------- +# Options affecting file encoding +# ------------------------------- +with section("encode"): + + # If true, emit the unicode byte-order mark (BOM) at the start of the file + emit_byteorder_mark = False + + # Specify the encoding of the input file. Defaults to utf-8 + input_encoding = "utf-8" + + # Specify the encoding of the output file. Defaults to utf-8. Note that cmake + # only claims to support utf-8 so be careful when using anything else + output_encoding = "utf-8" + +# ------------------------------------- +# Miscellaneous configurations options. +# ------------------------------------- +with section("misc"): + + # A dictionary containing any per-command configuration overrides. Currently + # only `command_case` is supported. + per_command = {"ToolchainLibraryFinder": {"command_case": "unchanged"}} diff --git a/CMakeLists.txt b/CMakeLists.txt index e6b8a6d..5284230 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,60 +1,125 @@ -# We use additional modules for the NUClear roles system -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") +#[[ +MIT License + +Copyright (c) 2016 NUbots + +This file is part of the NUbots codebase. +See https://github.com/NUbots/NUbots for further info. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -# We need NUClear -FIND_PACKAGE(NUClear REQUIRED) -INCLUDE_DIRECTORIES(SYSTEM ${NUClear_INCLUDE_DIR}) +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +# We use additional modules for the NUClear roles system +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") # Set to off to ignore building tests -OPTION(BUILD_TESTS "Builds all of the tests for each module." OFF) +option(BUILD_TESTS "Builds all of the tests for each module." OFF) -# Set to on to build as shared libraries -OPTION(NUCLEAR_SHARED_BUILD "Build each module as a separate shared library." ON) +# The ways we can link the libraries together +if(NOT NUCLEAR_LINK_TYPE) + set(NUCLEAR_LINK_TYPE + SHARED + CACHE STRING "Choose method to link the binary" FORCE + ) + # Set the possible values of build type for cmake-gui + set_property(CACHE NUCLEAR_LINK_TYPE PROPERTY STRINGS "SHARED" "STATIC" "OBJECT") +endif() -# Our banner file for placing at the top of the roles -SET(NUCLEAR_ROLE_BANNER_FILE "${CMAKE_CURRENT_SOURCE_DIR}/roles/banner.png" CACHE PATH "The path the banner to print at the start of each role execution") +# RPath variables use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) -# Our location of our nuclear roles directory -SET(NUCLEAR_ROLES_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE PATH "The path to the nuclear roles system directory") +# Build the RPATH into the binary before install +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -SET(NUCLEAR_ADDITIONAL_SHARED_LIBRARIES "" CACHE STRING "Additional libraries used when linking roles, extensions, and utilities") +# Make OSX use the same RPATH as everyone else +set(CMAKE_MACOSX_RPATH ON) -SET(NUCLEAR_TEST_LIBRARIES "" CACHE STRING "Additional libraries used when linking module tests") +# Add some useful places to the RPATH. These will allow the binary to run from the build folder +set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} lib/ ../lib/ bin/lib) -# Our variables that are used to locate the shared, module, and message folders -# They are given relative to the current project directory -SET(NUCLEAR_MODULE_DIR "module" CACHE PATH "The path to the module directory for NUClear") -SET(NUCLEAR_MESSAGE_DIR "shared/message" CACHE PATH "The path to the message directory for NUClear") -SET(NUCLEAR_UTILITY_DIR "shared/utility" CACHE PATH "The path to the utility dir for NUClear") -SET(NUCLEAR_EXTENSION_DIR "shared/extension" CACHE PATH "The path to the extension dir for NUClear") +# Our banner file for placing at the top of the roles +set(NUCLEAR_ROLE_BANNER_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/roles/banner.png" + CACHE PATH "The path the banner to print at the start of each role execution" +) + +# We need -fPIC for all code +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Our variables that are used to locate the shared, module, and message folders They are given relative to the current +# project directory +set(NUCLEAR_MODULE_DIR + "module" + CACHE PATH "The path to the module directory for NUClear" +) +set(NUCLEAR_ROLES_DIR + "roles" + CACHE PATH "The path to the nuclear roles system directory" +) +set(NUCLEAR_SHARED_DIR + "shared" + CACHE PATH "The path to the shared directory for NUClear" +) +set(NUCLEAR_MESSAGE_DIR + "${NUCLEAR_SHARED_DIR}/message" + CACHE PATH "The path to the message directory for NUClear" +) +set(NUCLEAR_UTILITY_DIR + "${NUCLEAR_SHARED_DIR}/utility" + CACHE PATH "The path to the utility dir for NUClear" +) +set(NUCLEAR_EXTENSION_DIR + "${NUCLEAR_SHARED_DIR}/extension" + CACHE PATH "The path to the extension dir for NUClear" +) # You generally shouldn't have to change these -MARK_AS_ADVANCED(NUCLEAR_ROLE_BANNER_FILE - NUCLEAR_MODULE_DIR - NUCLEAR_MESSAGE_DIR - NUCLEAR_UTILITY_DIR - NUCLEAR_EXTENSION_DIR) +mark_as_advanced( + NUCLEAR_MODULE_DIR + NUCLEAR_ROLES_DIR + NUCLEAR_SHARED_DIR + NUCLEAR_MESSAGE_DIR + NUCLEAR_UTILITY_DIR + NUCLEAR_EXTENSION_DIR + NUCLEAR_ROLE_BANNER_FILE +) # Make our shared directory to output files too -FILE(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/shared) - -# Settings for the compiler to make NUClear work -INCLUDE(NUClearCompilerSettings) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/${NUCLEAR_SHARED_DIR}) # Add the subdirectory for our messages -ADD_SUBDIRECTORY("message") +add_subdirectory("message") -# Add the subdirectory for our utilities -# Is after messages as it can use messages -ADD_SUBDIRECTORY("utility") +# * Add the subdirectory for our utilities. +# * This is after messages as it can use messages +add_subdirectory("utility") # Add the subdirectory for our extensions -ADD_SUBDIRECTORY("extension") +add_subdirectory("extension") # Add the subdirectory for our roles -ADD_SUBDIRECTORY("roles") +add_subdirectory("roles") -# Add the subdirectory for module -# This must be after roles as roles determines which modules to build -ADD_SUBDIRECTORY("module") +# * Add the subdirectory for module +# * This must be after roles as roles determines which modules to build +add_subdirectory("module") +if(BUILD_TESTS) + add_subdirectory(tests) +endif(BUILD_TESTS) diff --git a/README.md b/README.md index 7917ae0..22ef210 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # NUClear Roles + The NUClear roles system is a build and messaging system for the NUClear framework. It uses on CMake and Python to manage the construction of various executables made up of a selection of modules. These executables are called roles. @@ -8,15 +9,18 @@ Note that it utilises globbing to find the sources that are used for modules. So if you add or remove a file, make sure you rerun cmake so that it locates the new files. ## Setup + NUClear Roles is designed to exist as a part of another repository as either a git subtree or git submodule. In general subtrees are preferred to submodules as they allow you to make your own local changes to NUClear Roles and still be able to merge in upstream changes. To setup NUClearRoles as a subtree follow the following steps. - First create your repository where the NUClear Roles based system will live. - Once you have a repository to attach to run the following command from the root directory of the repository + ```bash git subtree add --prefix nuclear https://github.com/Fastcode/NUClearRoles.git master --squash ``` + - This will pull in NUClear Roles into the nuclear subdirectory ready for use by your system. Once you have added NUClearRoles to your codebase you must then configure a CMakeLists.txt file to use it. @@ -39,11 +43,13 @@ CMake code that depends on variables created by NUClear Roles should come after It is also recommended that you make a symlink from nuclear/b to ./b to make it easier to access the functionality of the b script. ### Dependencies + NUClear roles has several dependences that must be met before you are able to build the system. These dependencies are: - NUClear - Python3 with the following packages + - argparse - Pillow - stringcase - Optional dependencies: @@ -51,36 +57,41 @@ These dependencies are: - [Google Protobuf 3](https://developers.google.com/protocol-buffers/) (both c++ and python libraries) for Neutron messaging The Python dependencies can be installed using the provided requirements file: + ```bash pip3 install -r requirements.txt ``` ### Banner + NUClear roles generates an ansi coded banner at the top of ever role it runs. This banner file is created from an image file that is provided using the CMake cache variable `NUCLEAR_ROLE_BANNER_FILE` As the banner file is converted into ansi coloured unicode text, there are limitations on how the final result can look. To ensure that your banner looks good when rendered you should consider the following advice. + - Ensure that your image is 160px wide or less. - Many terminals when created are 80 columns wide. - The resolution of the created unicode text is half the resolution of the image. - This means a 160px wide image will make 80 columns of text. + Many terminals when created are 80 columns wide. + The resolution of the created unicode text is half the resolution of the image. + This means a 160px wide image will make 80 columns of text. - Try to make your logo in an editor using a 0.5 pixel aspect ratio. - The resolution of the image will be divided by four for the vertical axis. - This is done as text blocks are (almost) twice as high as they are wide. + The resolution of the image will be divided by four for the vertical axis. + This is done as text blocks are (almost) twice as high as they are wide. - Try to make smooth gradients. - When selecting colours for the text, each character can have 2 colours which are aranged into any combination of the 4 quadrants. - The unicode characters `█ ▄ ▐ ▞ ▟ ▚ ▌ ▙ ▀ ▜ ▛` are used to colour images so if the image has complex gradients the combination of these will look less clear. + When selecting colours for the text, each character can have 2 colours which are aranged into any combination of the 4 quadrants. + The unicode characters `█ ▄ ▐ ▞ ▟ ▚ ▌ ▙ ▀ ▜ ▛` are used to colour images so if the image has complex gradients the combination of these will look less clear. If you do not set your own banner file, ![the default banner](roles/banner.png) will be used ## Directories + There are six main directories that are used within the NUClear Roles system. + - `module` for where NUClear reactors - `message` for message types - `extension` for any NUClear DSL extensions @@ -90,6 +101,7 @@ There are six main directories that are used within the NUClear Roles system. - `tools` for any command line extensions for the `b` script ### Module + The module directory is where all NUClear Reactors are stored. This directory can be selected to be in a non default location by using the CMake cache variable `NUCLEAR_MODULE_DIR`. If this variable is not set it defaults to `module`. @@ -99,12 +111,14 @@ All modules must have the most outer namespace `module`. Take an example module `Camera` which exists in `namespace module::input`. This module must be located at `${NUCLEAR_MODULE_DIR}/input/Camera`. Within this module folder there are three directories that may hold code for the system. + - `${NUCLEAR_MODULE_DIR}/input/Camera/src` holds all of the source code for the module - This directory must contain a header file with the same name as the module followed by hpp, hh or h. E.g. Camera.h. This header must declare the NUClear::Reactor with the same name (Camera). - `${NUCLEAR_MODULE_DIR}/input/Camera/data` holds any non source code files. These will be copied to the build directory when building the code. - `${NUCLEAR_MODULE_DIR}/input/Camera/test` holds any unit test source code. ### Message + This directory can be selected to be in a non default location by using the CMake cache variable `NUCLEAR_MESSAGE_DIR`. If this variable is not set it defaults to `shared/message`. It is highly recommended that the message, utility and extension folders share a common parent folder. @@ -116,8 +130,8 @@ It must set the cmake cache variables `NUCLEAR_MESSAGE_LIBRARIES` to the librari If the message directory does not contain a CMakeLists.txt it will default to using the Neutron messaging system for messages. - #### Neutron Messaging System + While NUClear is able to use any c++ type as a message when emitting/triggering, it is advantageous to be able to serialise data in order to send data over a network or save to a file. The Neutron messaging system is designed to fill the gap using a system based on [Google Protocol Buffers](https://developers.google.com/protocol-buffers/). It uses protocol buffers for serialisation, however instead of using their c++ classes, it generates simplified structures to make it easier to use them within code. @@ -132,9 +146,10 @@ The messages sent to and from python are not serialised but instead, Neutron gen Note that the support for Python NUClear modules is still in alpha YMMV. ### Extension/Utility + These two directories are handled in a similarly to each other. -The extension directory can be selected to a non default location using the CMake cache variable `NUCLEAR_EXTENSION_DIR`. -The utility directory can be selected to a non default location using the CMake cache variable `NUCLEAR_UTILITY_DIR`. +The extension directory can be selected to a non default location using the CMake cache variable `NUCLEAR_EXTENSION_DIR`. +The utility directory can be selected to a non default location using the CMake cache variable `NUCLEAR_UTILITY_DIR`. If the directory contains a CMakeLists.txt file it will use this file to build the extensions/utilities for the system. This CMakeLists.txt file must ensure it sets `NUCLEAR_EXTENSION_INCLUDE_DIRS` and `NUCLEAR_EXTENSION_LIBRARIES` for extensions, or `NUCLEAR_UTILITY_INCLUDE_DIRS` and `NUCLEAR_UTILITY_LIBRARIES` for utilities. @@ -142,6 +157,7 @@ This CMakeLists.txt file must ensure it sets `NUCLEAR_EXTENSION_INCLUDE_DIRS` an If the folders do not contain a CMakeLists.txt file the default behaviour will be to build all c/cc/cpp files within that directory recursively. ### Roles + The NUClear roles directory contains a series of files named `.role` where `` is the name of the final binary that will be created. This directory can be selected to be in a non default location by using the CMake cache variable `NUCLEAR_ROLES_DIR`. If this variable is not set it defaults to `roles`. @@ -152,6 +168,7 @@ This is important to note as it means modules that have dependencies on other mo For example installing log handler modules should happen before installing other modules so their output can be seen. It will use this name to locate the module so the directory structure must match the name. An example of a role file would be: + ```cmake NUCLEAR_ROLE( # Some global modules that are useful @@ -163,10 +180,12 @@ NUCLEAR_ROLE( ) ``` + This role file would create an executable which had the modules `module::extension::FileWatcher`, `module::support::logging::ConsoleLogHandler` and `module::input::Camera`. This file is a cmake file so you are able to use # to declare comments. ## Tools and the `./b` script + NUClear Roles comes with a small python tool called `b` that lives in nuclear/b. This python tool is used to handle common functionality that you may wish to add to you system that takes advantage of information available in NUClear Roles. @@ -178,11 +197,13 @@ The ./b script will look for a `tools` directory above the location where the ac If it finds one, it will load all `.py` files in that directory and attempt to execute a register and run function on them. This allows the tools to register new functionality to the b script that can be accessed from the command line. For example three tools that exist in the NUbots codebase are: -- [this tool](https://github.com/NUbots/NUbots/blob/master/tools/install.py) installs NUClear roles systems onto remote systems using rsync -- [this tool](https://github.com/NUbots/NUbots/blob/master/tools/format.py) that formats all files using clang-format -- [this tool](https://github.com/NUbots/NUbots/blob/master/tools/decode.py) decodes `.nbs` files. + +- [this tool](https://github.com/NUbots/NUbots/blob/main/tools/install.py) installs NUClear roles systems onto remote systems using rsync +- [this tool](https://github.com/NUbots/NUbots/blob/main/tools/format.py) that formats all files using clang-format +- [this tool](https://github.com/NUbots/NUbots/blob/main/tools/decode.py) decodes `.nbs` files. Any of the tools that are created in this way have access to the b import which provides access to several useful variables + - `b.nuclear_dir` the directory that NUClear Roles is stored in - `b.project_dir` the directory above the NUClear Roles directory - `b.cmake_cache` the variables stored in the cmake cache. It attempts to find this either in the `cwd` or in the `build` @@ -190,6 +211,7 @@ Any of the tools that are created in this way have access to the b import which - `b.source_dir` the location of the project source directory as reported by the cmake cache ## NUClear Modules + `TODO` Variables @@ -199,6 +221,7 @@ SOURCES DATA_FILES ## Other Useful Variables + Additional there are options that can be set in NUClear Roles using CMake Cache variables. These options can influence how NUClear Roles generates systems. @@ -211,16 +234,17 @@ TODO descriptions ``` ## NUClear Binary Stream (nbs) files + To make it easier to serialise streams of messages for storage sharing and playback, NUClear Roles defines a format for serialising messages to files. This format is based on the Neutron messaging system and NUClear's networking protocol. An `nbs` file has the following frames repeated continuously. | Name | Type | Description | -|-----------|---------|-----------------------------------------------------------------------------------| +| --------- | ------- | --------------------------------------------------------------------------------- | | header | char[3] | the header sequence 0xE2, 0x98, 0xA2 (the radioactive symbol ☢ in utf-8) | | size | uint32 | the size of the frame after this byte in bytes | | timestamp | uint64 | the timestamp of this frame in microseconds. Does not have to be a utc timestamp. | | hash | uint64 | a 64 bit hash that identifies the type of the message | -| payload | char* | the serialised payload bytes | +| payload | char\* | the serialised payload bytes | All values within this format are little endian. diff --git a/b.py b/b.py index d408dd2..81df324 100755 --- a/b.py +++ b/b.py @@ -1,9 +1,38 @@ #!/usr/bin/env python3 -import sys -import os +# +# MIT License +# +# Copyright (c) 2013 NUbots +# +# This file is part of the NUbots codebase. +# See https://github.com/NUbots/NUbots for further info. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# import argparse +import os import pkgutil import re +import subprocess +import sys + +from dependencies import find_dependency, install_dependency # Don't make .pyc files sys.dont_write_bytecode = True @@ -13,31 +42,31 @@ project_dir = os.path.dirname(nuclear_dir) # Get the tools directories to find b modules -nuclear_tools_path = os.path.join(nuclear_dir, 'tools') -user_tools_path = os.path.join(project_dir, 'tools') +nuclear_tools_path = os.path.join(nuclear_dir, "tools") +user_tools_path = os.path.join(project_dir, "tools") # Build our cmake cache cmake_cache = {} # Try to find our cmake cache file in the pwd -if os.path.isfile('CMakeCache.txt'): - with open('CMakeCache.txt', 'r') as f: +if os.path.isfile("CMakeCache.txt"): + with open("CMakeCache.txt", "r") as f: cmake_cache_text = f.readlines() # Look for a build directory else: - dirs = ['build'] + dirs = ["build", os.path.join(os.pardir, "build")] try: - dirs.extend([os.path.join('build', f) for f in os.listdir('build')]) + dirs.extend([os.path.join("build", f) for f in os.listdir("build")]) except FileNotFoundError: pass for d in dirs: - if os.path.isfile(os.path.join(project_dir, d, 'CMakeCache.txt')): - with open(os.path.join(project_dir, d, 'CMakeCache.txt'), 'r') as f: + if os.path.isfile(os.path.join(project_dir, d, "CMakeCache.txt")): + with open(os.path.join(project_dir, d, "CMakeCache.txt"), "r") as f: cmake_cache_text = f.readlines() - break; + break # If we still didn't find anything try: @@ -52,64 +81,131 @@ l = l.strip() # Remove lines that are comments - if len(l) > 0 and not l.startswith('//') and not l.startswith('#'): + if len(l) > 0 and not l.startswith("//") and not l.startswith("#"): # Extract our variable name from our values - g = re.match(r'([a-zA-Z_$][a-zA-Z_.$0-9-]*):(\w+)=(.*)', l).groups() + g = re.match(r"([a-zA-Z_$][a-zA-Z_.$0-9-]*):(\w+)=(.*)", l).groups() # Store our value and split it into a list if it is a list - cmake_cache[g[0]] = g[2] if ';' not in g[2].strip(';') else g[2].strip(';').split(';'); + cmake_cache[g[0]] = g[2] if ";" not in g[2].strip(";") else g[2].strip(";").split(";") # Try to find our source and binary directories try: - binary_dir = cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + '_BINARY_DIR'] + binary_dir = cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + "_BINARY_DIR"] except KeyError: binary_dir = None try: - source_dir = cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + '_SOURCE_DIR'] + source_dir = cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + "_SOURCE_DIR"] except: source_dir = project_dir if __name__ == "__main__": - if (binary_dir is not None): - # Print some information for the user - print("b script for", cmake_cache["CMAKE_PROJECT_NAME"]) - print("\tSource:", cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + '_SOURCE_DIR']) - print("\tBinary:", cmake_cache[cmake_cache["CMAKE_PROJECT_NAME"] + '_BINARY_DIR']) - print() - - # Add our builtin tools to the path and user tools - sys.path.append(nuclear_tools_path) - sys.path.append(user_tools_path) + # Prepend nuclear and user tools to the path, so we prefer our packages + sys.path.insert(0, nuclear_tools_path) + sys.path.insert(0, user_tools_path) # Root parser information - command = argparse.ArgumentParser(description='This script is an optional helper script for performing common tasks for working with the NUClear roles system.') - subcommands = command.add_subparsers(dest='command') - subcommands.help = "The command to run from the script. See each help for more information." - - # Get all of the packages that are in the build tools - modules = pkgutil.iter_modules(path=[nuclear_tools_path, user_tools_path]) - - # Our tools dictionary + command = argparse.ArgumentParser( + description="This script is an optional helper script for performing common tasks for working with the NUClear roles system." + ) + subcommands = command.add_subparsers( + dest="command", help="The command to run from the script. See each help for more information." + ) + subcommands.required = True + + # Look thorough our tools directories and find all the files and folders that could be a command + candidates = [] + for path in [user_tools_path, nuclear_tools_path]: + for dirpath, dnames, fnames in os.walk(path): + + # Get all the possible commands they might want to run based on folders and python files + candidates.extend( + [ + os.path.relpath(os.path.join(dirpath, os.path.splitext(f)[0]), path).split(os.sep) + for f in fnames + if f != "__init__.py" and os.path.splitext(f)[1] == ".py" + ] + ) + candidates.extend( + [ + os.path.relpath(os.path.join(dirpath, d), path).split(os.sep) + for d in dnames + if os.path.isfile(os.path.join(dirpath, d, "__init__.py")) + ] + ) + + # See if we can find a command that matches what we want to do and sort so the longest match is first + useable = [c for c in candidates if sys.argv[1 : len(c) + 1] == c] + useable.sort(key=lambda x: len(x), reverse=True) + + for components in useable: + if sys.argv[1 : len(components) + 1] == components: + loader = pkgutil.find_loader(".".join(components)) + if loader: + try: + module = loader.load_module() + if hasattr(module, "register") and hasattr(module, "run"): + + # Build up the base subcommands to this point + subcommand = subcommands + for c in components[:-1]: + subcommand = subcommand.add_parser(c).add_subparsers( + dest="{}_command".format(c), + help="Commands related to working with {} functionality".format(c), + ) + subcommand.required = True + + module.register(subcommand.add_parser(components[-1])) + module.run(**vars(command.parse_args())) + + # We're done, exit + exit(0) + + except ModuleNotFoundError as e: + print(f'missing command dependency "{e.name}"') + + dependency = find_dependency(e.name, user_tools_path) + package = dependency["version"] + + print(f'installing missing dependency "{package}"...') + print() + + install_dependency(package) + + # Try re-running the current command now that the library exists + sys.exit(subprocess.call([sys.executable, *sys.argv])) + + # If we reach this point, we couldn't find a tool to use. + # In this case we need to look through all the tools so we can register them all. + # This will provide a complete help for the function call so the user can try again tools = {} - - # Loop through all the modules we have to set them up in the parser - for loader, module_name, ispkg in modules: - - # Get our module, class name and registration function - module = loader.find_module(module_name).load_module(module_name) - tool = getattr(module, 'run') - register = getattr(module, 'register') - - # Let the tool register it's arguments - register(subcommands.add_parser(module_name)) - - # Associate our module_name with this tool - tools[module_name] = tool - - # Parse our arguments - args = command.parse_args() - - # Pass to our tool - tools[args.command](**vars(args)) + for components in candidates: + try: + loader = pkgutil.find_loader(".".join(components)) + if loader: + module = loader.load_module() + if hasattr(module, "register") and hasattr(module, "run"): + + subcommand = subcommands + tool = tools + for c in components[:-1]: + if c in tool: + tool, subcommand = tool[c] + else: + subcommand = subcommand.add_parser(c).add_subparsers( + dest="{}_command".format(c), + help="Commands related to working with {} functionality".format(c), + ) + subcommand.required = True + tool[c] = ({}, subcommand) + tool = tool[c][0] + + module.register(subcommand.add_parser(components[-1])) + except ModuleNotFoundError as e: + pass + except BaseException as e: + pass + + # Given what we know, this will fail here and give the user some help + command.parse_args() diff --git a/cmake/Modules/FindEigen3.cmake b/cmake/Modules/FindEigen3.cmake deleted file mode 100644 index 422b7d0..0000000 --- a/cmake/Modules/FindEigen3.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (C) 2013-2016 Trent Houliston -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -INCLUDE(ToolchainLibraryFinder) -ToolchainLibraryFinder(NAME Eigen3 - HEADER Eigen/Core - PATH_SUFFIX eigen3 -) diff --git a/cmake/Modules/FindNUClear.cmake b/cmake/Modules/FindNUClear.cmake deleted file mode 100644 index c442231..0000000 --- a/cmake/Modules/FindNUClear.cmake +++ /dev/null @@ -1,5 +0,0 @@ -INCLUDE(ToolchainLibraryFinder) -ToolchainLibraryFinder(NAME NUClear - HEADER nuclear - LIBRARY nuclear -) diff --git a/cmake/Modules/FindPythonLibsNew.cmake b/cmake/Modules/FindPythonLibsNew.cmake index 894f530..6f9d969 100644 --- a/cmake/Modules/FindPythonLibsNew.cmake +++ b/cmake/Modules/FindPythonLibsNew.cmake @@ -1,79 +1,64 @@ -# - Find python libraries -# This module finds the libraries corresponding to the Python interpeter -# FindPythonInterp provides. -# This code sets the following variables: +# * Find python libraries This module finds the libraries corresponding to the Python interpeter FindPythonInterp +# provides. This code sets the following variables: # -# PYTHONLIBS_FOUND - have the Python libs been found -# PYTHON_PREFIX - path to the Python installation -# PYTHON_LIBRARIES - path to the python library -# PYTHON_INCLUDE_DIRS - path to where Python.h is found -# PYTHON_MODULE_EXTENSION - lib extension, e.g. '.so' or '.pyd' -# PYTHON_MODULE_PREFIX - lib name prefix: usually an empty string -# PYTHON_SITE_PACKAGES - path to installation site-packages -# PYTHON_IS_DEBUG - whether the Python interpreter is a debug build +# PYTHONLIBS_FOUND - have the Python libs been found PYTHON_PREFIX - path to the Python +# installation PYTHON_LIBRARIES - path to the python library PYTHON_INCLUDE_DIRS - path to where +# Python.h is found PYTHON_MODULE_EXTENSION - lib extension, e.g. '.so' or '.pyd' PYTHON_MODULE_PREFIX - lib +# name prefix: usually an empty string PYTHON_SITE_PACKAGES - path to installation site-packages PYTHON_IS_DEBUG - +# whether the Python interpreter is a debug build # -# Thanks to talljimbo for the patch adding the 'LDVERSION' config -# variable usage. +# Thanks to talljimbo for the patch adding the 'LDVERSION' config variable usage. -#============================================================================= -# Copyright 2001-2009 Kitware, Inc. -# Copyright 2012 Continuum Analytics, Inc. +# ============================================================================= +# Copyright 2001-2009 Kitware, Inc. Copyright 2012 Continuum Analytics, Inc. # # All rights reserved. # -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: # -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. +# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. # -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. +# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. # -# * Neither the names of Kitware, Inc., the Insight Software Consortium, -# nor the names of their contributors may be used to endorse or promote -# products derived from this software without specific prior written -# permission. +# * Neither the names of Kitware, Inc., the Insight Software Consortium, nor the names of their contributors may be used +# to endorse or promote products derived from this software without specific prior written permission. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#============================================================================= +# ============================================================================= if(PYTHONLIBS_FOUND) - return() + return() endif() # Use the Python interpreter to find the libs. if(PythonLibsNew_FIND_REQUIRED) - find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} REQUIRED) + find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} REQUIRED) else() - find_package(PythonInterp ${PythonLibsNew_FIND_VERSION}) + find_package(PythonInterp ${PythonLibsNew_FIND_VERSION}) endif() if(NOT PYTHONINTERP_FOUND) - set(PYTHONLIBS_FOUND FALSE) - return() + set(PYTHONLIBS_FOUND FALSE) + return() endif() -# According to http://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter -# testing whether sys has the gettotalrefcount function is a reliable, cross-platform -# way to detect a CPython debug interpreter. +# According to http://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter testing whether sys has +# the gettotalrefcount function is a reliable, cross-platform way to detect a CPython debug interpreter. # -# The library suffix is from the config var LDVERSION sometimes, otherwise -# VERSION. VERSION will typically be like "2.7" on unix, and "27" on windows. -execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "from distutils import sysconfig as s;import sys;import struct; +# The library suffix is from the config var LDVERSION sometimes, otherwise VERSION. VERSION will typically be like "2.7" +# on unix, and "27" on windows. +execute_process( + COMMAND + "${PYTHON_EXECUTABLE}" "-c" "from distutils import sysconfig as s;import sys;import struct; print('.'.join(str(v) for v in sys.version_info)); print(sys.prefix); print(s.get_python_inc(plat_specific=True)); @@ -83,18 +68,18 @@ print(hasattr(sys, 'gettotalrefcount')+0); print(struct.calcsize('@P')); print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); " - RESULT_VARIABLE _PYTHON_SUCCESS - OUTPUT_VARIABLE _PYTHON_VALUES - ERROR_VARIABLE _PYTHON_ERROR_VALUE - OUTPUT_STRIP_TRAILING_WHITESPACE) + RESULT_VARIABLE _PYTHON_SUCCESS + OUTPUT_VARIABLE _PYTHON_VALUES + ERROR_VARIABLE _PYTHON_ERROR_VALUE + OUTPUT_STRIP_TRAILING_WHITESPACE +) if(NOT _PYTHON_SUCCESS MATCHES 0) - if(PythonLibsNew_FIND_REQUIRED) - message(FATAL_ERROR - "Python config failure:\n${_PYTHON_ERROR_VALUE}") - endif() - set(PYTHONLIBS_FOUND FALSE) - return() + if(PythonLibsNew_FIND_REQUIRED) + message(FATAL_ERROR "Python config failure:\n${_PYTHON_ERROR_VALUE}") + endif() + set(PYTHONLIBS_FOUND FALSE) + return() endif() # Convert the process output into a list @@ -109,18 +94,17 @@ list(GET _PYTHON_VALUES 5 PYTHON_IS_DEBUG) list(GET _PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) list(GET _PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) -# Make sure the Python has the same pointer-size as the chosen compiler -# Skip if CMAKE_SIZEOF_VOID_P is not defined +# Make sure the Python has the same pointer-size as the chosen compiler Skip if CMAKE_SIZEOF_VOID_P is not defined if(CMAKE_SIZEOF_VOID_P AND (NOT "${PYTHON_SIZEOF_VOID_P}" STREQUAL "${CMAKE_SIZEOF_VOID_P}")) - if(PythonLibsNew_FIND_REQUIRED) - math(EXPR _PYTHON_BITS "${PYTHON_SIZEOF_VOID_P} * 8") - math(EXPR _CMAKE_BITS "${CMAKE_SIZEOF_VOID_P} * 8") - message(FATAL_ERROR - "Python config failure: Python is ${_PYTHON_BITS}-bit, " - "chosen compiler is ${_CMAKE_BITS}-bit") - endif() - set(PYTHONLIBS_FOUND FALSE) - return() + if(PythonLibsNew_FIND_REQUIRED) + math(EXPR _PYTHON_BITS "${PYTHON_SIZEOF_VOID_P} * 8") + math(EXPR _CMAKE_BITS "${CMAKE_SIZEOF_VOID_P} * 8") + message(FATAL_ERROR "Python config failure: Python is ${_PYTHON_BITS}-bit, " + "chosen compiler is ${_CMAKE_BITS}-bit" + ) + endif() + set(PYTHONLIBS_FOUND FALSE) + return() endif() # The built-in FindPython didn't always give the version numbers @@ -136,62 +120,53 @@ string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES ${PYTHON_SITE_PACKAGES}) # TODO: All the nuances of CPython debug builds have not been dealt with/tested. if(PYTHON_IS_DEBUG) - set(PYTHON_MODULE_EXTENSION "_d${PYTHON_MODULE_EXTENSION}") + set(PYTHON_MODULE_EXTENSION "_d${PYTHON_MODULE_EXTENSION}") endif() if(CMAKE_HOST_WIN32) - set(PYTHON_LIBRARY - "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") - - # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the - # original python installation. They may be found relative to PYTHON_INCLUDE_DIR. - if(NOT EXISTS "${PYTHON_LIBRARY}") - get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) - set(PYTHON_LIBRARY - "${_PYTHON_ROOT}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") - endif() - - # raise an error if the python libs are still not found. - if(NOT EXISTS "${PYTHON_LIBRARY}") - message(FATAL_ERROR "Python libraries not found") - endif() + set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + + # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the original python installation. They + # may be found relative to PYTHON_INCLUDE_DIR. + if(NOT EXISTS "${PYTHON_LIBRARY}") + get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) + set(PYTHON_LIBRARY "${_PYTHON_ROOT}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") + endif() + + # raise an error if the python libs are still not found. + if(NOT EXISTS "${PYTHON_LIBRARY}") + message(FATAL_ERROR "Python libraries not found") + endif() elseif(APPLE) - set(PYTHON_LIBRARY - "${PYTHON_PREFIX}/lib/libpython${PYTHON_LIBRARY_SUFFIX}.dylib") + set(PYTHON_LIBRARY "${PYTHON_PREFIX}/lib/libpython${PYTHON_LIBRARY_SUFFIX}.dylib") else() - if(${PYTHON_SIZEOF_VOID_P} MATCHES 8) - set(_PYTHON_LIBS_SEARCH "${PYTHON_PREFIX}/lib64" "${PYTHON_PREFIX}/lib") - else() - set(_PYTHON_LIBS_SEARCH "${PYTHON_PREFIX}/lib") - endif() - #message(STATUS "Searching for Python libs in ${_PYTHON_LIBS_SEARCH}") - # Probably this needs to be more involved. It would be nice if the config - # information the python interpreter itself gave us were more complete. - find_library(PYTHON_LIBRARY - NAMES "python${PYTHON_LIBRARY_SUFFIX}" - PATHS ${_PYTHON_LIBS_SEARCH} - NO_DEFAULT_PATH) - - # If all else fails, just set the name/version and let the linker figure out the path. - if(NOT PYTHON_LIBRARY) - set(PYTHON_LIBRARY python${PYTHON_LIBRARY_SUFFIX}) - endif() + if(${PYTHON_SIZEOF_VOID_P} MATCHES 8) + set(_PYTHON_LIBS_SEARCH "${PYTHON_PREFIX}/lib64" "${PYTHON_PREFIX}/lib") + else() + set(_PYTHON_LIBS_SEARCH "${PYTHON_PREFIX}/lib") + endif() + # message(STATUS "Searching for Python libs in ${_PYTHON_LIBS_SEARCH}") Probably this needs to be more involved. It + # would be nice if the config information the python interpreter itself gave us were more complete. + find_library( + PYTHON_LIBRARY + NAMES "python${PYTHON_LIBRARY_SUFFIX}" + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH + ) + + # If all else fails, just set the name/version and let the linker figure out the path. + if(NOT PYTHON_LIBRARY) + set(PYTHON_LIBRARY python${PYTHON_LIBRARY_SUFFIX}) + endif() endif() -MARK_AS_ADVANCED( - PYTHON_LIBRARY - PYTHON_INCLUDE_DIR -) +mark_as_advanced(PYTHON_LIBRARY PYTHON_INCLUDE_DIR) + +# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the cache entries because they are meant to +# specify the location of a single library. We now set the variables listed by the documentation for this module. +set(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") +set(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") +set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") -# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the -# cache entries because they are meant to specify the location of a single -# library. We now set the variables listed by the documentation for this -# module. -SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") -SET(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") -SET(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") - -find_package_message(PYTHON - "Found PythonLibs: ${PYTHON_LIBRARY}" - "${PYTHON_EXECUTABLE}${PYTHON_VERSION}") +find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARY}" "${PYTHON_EXECUTABLE}${PYTHON_VERSION}") diff --git a/cmake/Modules/Findpybind11.cmake b/cmake/Modules/Findpybind11.cmake index 30cdd60..c297747 100644 --- a/cmake/Modules/Findpybind11.cmake +++ b/cmake/Modules/Findpybind11.cmake @@ -1,4 +1,2 @@ -INCLUDE(ToolchainLibraryFinder) -ToolchainLibraryFinder(NAME pybind11 - HEADER pybind11/pybind11.h -) +include(ToolchainLibraryFinder) +ToolchainLibraryFinder(NAME pybind11 HEADER pybind11/pybind11.h) diff --git a/cmake/Modules/GenerateNeutron.cmake b/cmake/Modules/GenerateNeutron.cmake new file mode 100644 index 0000000..b077793 --- /dev/null +++ b/cmake/Modules/GenerateNeutron.cmake @@ -0,0 +1,170 @@ +define_property( + TARGET + PROPERTY NEUTRON_CPP_SOURCE + BRIEF_DOCS "Generated neutron C++ source files" + FULL_DOCS "Generated neutron C++ source files" +) +define_property( + TARGET + PROPERTY NEUTRON_PROTOBUF_SOURCE + BRIEF_DOCS "Generated protobuf C/C++ source files" + FULL_DOCS "Generated protobuf C/C++ source files" +) +define_property( + TARGET + PROPERTY NEUTRON_PYTHON_SOURCE + BRIEF_DOCS "Generated protobuf python source files" + FULL_DOCS "Generated protobuf python source files" +) + +include(CMakeParseArguments) +function(GenerateNeutron) + # We need protobuf and python to generate the neutron messages + find_package(Protobuf REQUIRED) + find_package(PythonInterp 3 REQUIRED) + + # Set the path to our generating scripts + set(SCRIPT_SOURCE "${PROJECT_SOURCE_DIR}/nuclear/cmake/Scripts") + + # Files that are used to generate the neutron files + file(GLOB_RECURSE message_class_generator_files "${SCRIPT_SOURCE}/generator/**.py") + + # Extract the arguments from our function call + set(options, "") + set(oneValueArgs "PROTO" "PARENT_DIR" "BUILTIN_DIR" "BUILTIN_OUTPUT_DIR") + set(multiValueArgs "") + cmake_parse_arguments(NEUTRON "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Locations to store each of the output components + set(pb_out "${CMAKE_CURRENT_BINARY_DIR}/protobuf") + set(nt_out "${CMAKE_CURRENT_BINARY_DIR}/neutron") + set(py_out "${CMAKE_CURRENT_BINARY_DIR}/python") + + # Extract the components of the filename that we need + get_filename_component(file_we ${NEUTRON_PROTO} NAME_WE) + file(RELATIVE_PATH output_path ${NEUTRON_PARENT_DIR} ${NEUTRON_PROTO}) + get_filename_component(output_path ${output_path} DIRECTORY) + + # Make sure paths are normalised + cmake_path(SET NEUTRON_PROTO NORMALIZE "${NEUTRON_PROTO}") + cmake_path(SET NEUTRON_PARENT_DIR NORMALIZE "${NEUTRON_PARENT_DIR}") + cmake_path(SET NEUTRON_BUILTIN_DIR NORMALIZE "${NEUTRON_BUILTIN_DIR}") + cmake_path(SET NEUTRON_BUILTIN_OUTPUT_DIR NORMALIZE "${NEUTRON_BUILTIN_OUTPUT_DIR}") + cmake_path(SET pb NORMALIZE "${pb_out}/${output_path}/${file_we}") + cmake_path(SET py NORMALIZE "${py_out}/${output_path}/${file_we}") + cmake_path(SET nt NORMALIZE "${nt_out}/${output_path}/${file_we}") + + # Make sure the output paths exist + file(MAKE_DIRECTORY "${pb_out}/${output_path}") + file(MAKE_DIRECTORY "${nt_out}/${output_path}") + file(MAKE_DIRECTORY "${py_out}/${output_path}") + + # Name of the target that will be created for this neutron set(neutron_target ${file_we}_neutron) + string(REPLACE "/" "_" neutron_target "${output_path}_${file_we}_neutron") + string(REGEX REPLACE "^_([a-zA-Z0-9_]+)$" "\\1" neutron_target ${neutron_target}) + + # + # DEPENDENCIES + # + # Ideally ninja would do this at runtime, but for now we have to work out what dependencies each of the protocol + # buffer files have. If these change we just have to hope that it'll work until someone runs cmake again + execute_process( + COMMAND + ${PROTOBUF_PROTOC_EXECUTABLE} --dependency_out=${CMAKE_CURRENT_BINARY_DIR}/dependencies.txt + --descriptor_set_out=${CMAKE_CURRENT_BINARY_DIR}/descriptor.pb -I${NEUTRON_PARENT_DIR} -I${NEUTRON_BUILTIN_DIR} + ${NEUTRON_PROTO} + ) + file(READ "${CMAKE_CURRENT_BINARY_DIR}/dependencies.txt" dependencies) + string(REGEX REPLACE "\\\\\n" ";" dependencies ${dependencies}) + file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/dependencies.txt" "${CMAKE_CURRENT_BINARY_DIR}/descriptor.pb") + + # Clean our dependency list + foreach(depend ${dependencies}) + string(STRIP ${depend} depend) + string(REGEX REPLACE "^[^:]*:[ \t\r\n]*" "" depend ${depend}) + file(RELATIVE_PATH depend_rel ${NEUTRON_PARENT_DIR} ${depend}) + + # * Add a dependency to the target that generates the neutron for this dependency + # * We specifically exclude adding dependencies for system protobufs (these have "/google/protobuf/" in their path) + # * and NUClearRoles builtin protobufs (these have "/nuclear/message/proto/" in their path) + get_filename_component(depend_we ${depend_rel} NAME_WE) + get_filename_component(output_path ${depend_rel} DIRECTORY) + string(REPLACE "/" "_" target_depend "${output_path}_${depend_we}_neutron") + string(REGEX REPLACE "^_([a-zA-Z0-9_]+)$" "\\1" target_depend ${target_depend}) + + string(FIND "${depend}" "/nuclear/message/proto/" is_builtin) + string(FIND "${depend}" "/google/protobuf/" is_system) + if(is_builtin EQUAL -1 + AND is_system EQUAL -1 + AND NOT neutron_target STREQUAL target_depend + ) + list(APPEND target_depends ${target_depend}) + endif() + + if(depend_rel MATCHES "^\\.\\.") + # Absolute dependencies + cmake_path(SET depend NORMALIZE "${depend}") + list(APPEND source_depends ${depend}) + list(APPEND binary_depends ${depend}) + else() + # Relative dependencies + list(APPEND source_depends ${NEUTRON_PARENT_DIR}/${depend_rel}) + list(APPEND binary_depends ${pb_out}/${depend_rel}) + endif() + endforeach() + + # + # PROTOCOL BUFFERS + # + # Repackage our protocol buffers so they don't collide with the actual classes when we make our c++ protobuf classes + # by adding protobuf to the package + add_custom_command( + OUTPUT "${pb}.proto" + COMMAND ${PYTHON_EXECUTABLE} ARGS "${SCRIPT_SOURCE}/repackage_message.py" "${NEUTRON_PROTO}" "${pb}.proto" + WORKING_DIRECTORY "${SCRIPT_SOURCE}" + DEPENDS "${SCRIPT_SOURCE}/repackage_message.py" ${NEUTRON_PROTO} + COMMENT "Repackaging protobuf ${NEUTRON_PROTO}" + ) + + # Run the protocol buffer compiler on these new protobufs + add_custom_command( + OUTPUT "${pb}.pb.cc" "${pb}.pb.h" "${py}_pb2.py" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ARGS --cpp_out=lite:${pb_out} --python_out=${py_out} -I${pb_out} + -I${NEUTRON_BUILTIN_DIR} "${pb}.proto" + DEPENDS ${binary_depends} "${pb}.proto" ${target_depends} + COMMENT "Compiling protocol buffer ${NEUTRON_PROTO}" + ) + + # + # NEUTRONS + # + # Extract the protocol buffer information so we can generate code off it + add_custom_command( + OUTPUT "${nt}.pb" + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ARGS --descriptor_set_out="${nt}.pb" -I${NEUTRON_PARENT_DIR} + -I${NEUTRON_BUILTIN_DIR} ${NEUTRON_PROTO} + DEPENDS ${source_depends} + COMMENT "Extracting protocol buffer information from ${NEUTRON_PROTO}" + ) + + # Build our c++ class from the extracted information + add_custom_command( + OUTPUT "${nt}.cpp" "${nt}.py.cpp" "${nt}.hpp" + COMMAND ${CMAKE_COMMAND} -E env NEUTRON_BUILTIN_DIR=${NEUTRON_BUILTIN_OUTPUT_DIR} ${PYTHON_EXECUTABLE} ARGS + "${SCRIPT_SOURCE}/build_message_class.py" "${nt}" + WORKING_DIRECTORY "${nt_out}" + DEPENDS "${SCRIPT_SOURCE}/build_message_class.py" ${message_class_generator_files} "${nt}.pb" + nuclear_message_builtins + COMMENT "Building classes for ${NEUTRON_PROTO}" + ) + + # Create a target for people to depend on + add_custom_target(${neutron_target} DEPENDS "${pb}.pb.cc" "${pb}.pb.h" "${nt}.cpp" "${nt}.hpp" "${py}_pb2.py") + set_target_properties( + ${neutron_target} + PROPERTIES NEUTRON_CPP_SOURCE "${nt}.cpp;${nt}.hpp" + NEUTRON_PROTOBUF_SOURCE "${pb}.pb.cc;${pb}.pb.h" + NEUTRON_PYTHON_SOURCE "${py}_pb2.py" + ) + +endfunction(GenerateNeutron) diff --git a/cmake/Modules/HeaderLibrary.cmake b/cmake/Modules/HeaderLibrary.cmake index ed52819..05dab9d 100644 --- a/cmake/Modules/HeaderLibrary.cmake +++ b/cmake/Modules/HeaderLibrary.cmake @@ -1,58 +1,69 @@ -INCLUDE(CMakeParseArguments) -FUNCTION(HeaderLibrary) - # Extract the arguments from our function call - SET(options, "") - SET(oneValueArgs "NAME") - SET(multiValueArgs "HEADER" "PATH_SUFFIX" "URL") - CMAKE_PARSE_ARGUMENTS(PACKAGE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # Clear our required_vars variable - UNSET(required_vars) - - # Find our include path - FIND_PATH("${PACKAGE_NAME}_INCLUDE_DIR" - NAMES ${PACKAGE_HEADER} - DOC "The ${PACKAGE_NAME} include directory" - PATHS "${CMAKE_BINARY_DIR}/include" - PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} - ) - - # File doesn't exist in standard search paths, download it - IF(NOT ${PACKAGE_NAME}_INCLUDE_DIR) - SET(OUTPUT_DIR "${CMAKE_BINARY_DIR}/include") - - # Create the output folder if it doesn't already exist - IF(NOT EXISTS "${OUTPUT_DIR}") - FILE(MAKE_DIRECTORY "${OUTPUT_DIR}") - ENDIF() - - # Download file. - FILE(DOWNLOAD "${PACKAGE_URL}" "${OUTPUT_DIR}/${PACKAGE_HEADER}" STATUS ${PACKAGE_NAME}_STATUS) - - LIST(GET ${PACKAGE_NAME}_STATUS 0 ${PACKAGE_NAME}_STATUS_CODE) - - # Parse download status - IF(${PACKAGE_NAME}_STATUS_CODE EQUAL 0) - MESSAGE(STATUS "Successfully downloaded ${PACKAGE_NAME} library.") - - SET(${PACKAGE_NAME}_INCLUDE_DIR "${OUTPUT_DIR}") - - ELSE() - MESSAGE(ERROR "Failed to download ${PACKAGE_NAME} library.") - ENDIF() - ENDIF() - - # Setup and export our variables - SET(required_vars ${required_vars} "${PACKAGE_NAME}_INCLUDE_DIR") - SET(${PACKAGE_NAME}_INCLUDE_DIRS ${${PACKAGE_NAME}_INCLUDE_DIR} PARENT_SCOPE) - MARK_AS_ADVANCED(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) - - # Find the package - INCLUDE(FindPackageHandleStandardArgs) - FIND_PACKAGE_HANDLE_STANDARD_ARGS(${PACKAGE_NAME} - FOUND_VAR ${PACKAGE_NAME}_FOUND - REQUIRED_VARS ${required_vars} - VERSION_VAR ${PACKAGE_NAME}_VERSION - ) - -ENDFUNCTION(HeaderLibrary) +include(CMakeParseArguments) +function(HeaderLibrary) + # Extract the arguments from our function call + set(options, "") + set(oneValueArgs "NAME") + set(multiValueArgs "HEADER" "PATH_SUFFIX" "URL") + cmake_parse_arguments(PACKAGE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Clear our required_vars variable + unset(required_vars) + + # Find our include path + find_path( + "${PACKAGE_NAME}_INCLUDE_DIR" + NAMES ${PACKAGE_HEADER} + DOC "The ${PACKAGE_NAME} include directory" + PATHS "${CMAKE_BINARY_DIR}/include" + PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} + ) + + # File doesn't exist in standard search paths, download it + if(NOT ${PACKAGE_NAME}_INCLUDE_DIR) + set(OUTPUT_DIR "${CMAKE_BINARY_DIR}/include") + + # Create the output folder if it doesn't already exist + if(NOT EXISTS "${OUTPUT_DIR}") + file(MAKE_DIRECTORY "${OUTPUT_DIR}") + endif() + + # Download file. + file(DOWNLOAD "${PACKAGE_URL}" "${OUTPUT_DIR}/${PACKAGE_HEADER}" STATUS ${PACKAGE_NAME}_STATUS) + + list(GET ${PACKAGE_NAME}_STATUS 0 ${PACKAGE_NAME}_STATUS_CODE) + + # Parse download status + if(${PACKAGE_NAME}_STATUS_CODE EQUAL 0) + message(STATUS "Successfully downloaded ${PACKAGE_NAME} library.") + + set(${PACKAGE_NAME}_INCLUDE_DIR + "${OUTPUT_DIR}" + CACHE PATH "The ${PACKAGE_NAME} include directory" FORCE + ) + + else() + message(ERROR "Failed to download ${PACKAGE_NAME} library.") + endif() + endif() + + # Setup and export our variables + set(required_vars ${required_vars} "${PACKAGE_NAME}_INCLUDE_DIR") + set(${PACKAGE_NAME}_INCLUDE_DIRS + ${${PACKAGE_NAME}_INCLUDE_DIR} + PARENT_SCOPE + ) + mark_as_advanced(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) + + # Find the package + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( + ${PACKAGE_NAME} + FOUND_VAR ${PACKAGE_NAME}_FOUND + REQUIRED_VARS ${required_vars} + VERSION_VAR ${PACKAGE_NAME}_VERSION + ) + + add_library(${PACKAGE_NAME}::${PACKAGE_NAME} INTERFACE IMPORTED) + target_include_directories(${PACKAGE_NAME}::${PACKAGE_NAME} SYSTEM INTERFACE ${${PACKAGE_NAME}_INCLUDE_DIR}) + +endfunction(HeaderLibrary) diff --git a/cmake/Modules/NUClearCompilerSettings.cmake b/cmake/Modules/NUClearCompilerSettings.cmake deleted file mode 100644 index eeb769f..0000000 --- a/cmake/Modules/NUClearCompilerSettings.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# Default to do a debug build -IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE Debug CACHE STRING - "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." - FORCE) -ENDIF() - -# RPath variables -# use, i.e. don't skip the full RPATH for the build tree -SET(CMAKE_SKIP_BUILD_RPATH FALSE) - -# Build the RPATH into the binary before install -SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - -# Make OSX use the same RPATH as everyone else -SET(CMAKE_MACOSX_RPATH ON) - -# Add some useful places to the RPATH -# These will allow the binary to run from the build folder -SET(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} lib/ ../lib/ bin/lib) - -IF(NOT MSVC) - # Compilation must be done with c++14 for NUClear to work - ADD_COMPILE_OPTIONS(-std=c++14 -fPIC) -ENDIF() diff --git a/cmake/Modules/ToolchainLibraryFinder.cmake b/cmake/Modules/ToolchainLibraryFinder.cmake index a61e806..b61c570 100644 --- a/cmake/Modules/ToolchainLibraryFinder.cmake +++ b/cmake/Modules/ToolchainLibraryFinder.cmake @@ -1,97 +1,195 @@ -INCLUDE(CMakeParseArguments) -FUNCTION(ToolchainLibraryFinder) - - # Extract the arguments from our function call - SET(options, "") - SET(oneValueArgs "NAME") - SET(multiValueArgs "HEADER" "LIBRARY" "PATH_SUFFIX" "BINARY" "VERSION_FILE" "VERSION_BINARY_ARGUMENTS" "VERSION_REGEX") - CMAKE_PARSE_ARGUMENTS(PACKAGE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # Clear our required_vars variable - UNSET(required_vars) - - # Find our include path from our named headers - IF(PACKAGE_HEADER) - - # Find our include path - FIND_PATH("${PACKAGE_NAME}_INCLUDE_DIR" - NAMES ${PACKAGE_HEADER} - DOC "The ${PACKAGE_NAME} (${PACKAGE_LIBRARY}) include directory" - PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} - ) - - # Setup and export our variables - SET(required_vars ${required_vars} "${PACKAGE_NAME}_INCLUDE_DIR") - SET(${PACKAGE_NAME}_INCLUDE_DIRS ${${PACKAGE_NAME}_INCLUDE_DIR} PARENT_SCOPE) - MARK_AS_ADVANCED(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) - - ENDIF(PACKAGE_HEADER) - - # Find our library from the named library files - IF(PACKAGE_LIBRARY) - FIND_LIBRARY("${PACKAGE_NAME}_LIBRARY" - NAMES ${PACKAGE_LIBRARY} - PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} - DOC "The ${PACKAGE_NAME} (${PACKAGE_LIBRARY}) library" - ) - - # Setup and export our variables - SET(required_vars ${required_vars} "${PACKAGE_NAME}_LIBRARY") - SET(${PACKAGE_NAME}_LIBRARIES ${${PACKAGE_NAME}_LIBRARY} PARENT_SCOPE) - MARK_AS_ADVANCED(${PACKAGE_NAME}_LIBRARY ${PACKAGE_NAME}_LIBRARIES) - - ENDIF(PACKAGE_LIBRARY) - - # Find our binary from the named binary files - IF(PACKAGE_BINARY) - FIND_PROGRAM("${PACKAGE_NAME}_BINARY" - NAMES ${PACKAGE_BINARY} - PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} - DOC "The ${PACKAGE_NAME} (${PACKAGE_BINARY}) executable prgram" - ) - - # Setup and export our variables - SET(required_vars ${required_vars} "${PACKAGE_NAME}_BINARY") - SET(${PACKAGE_NAME}_BINARY ${${PACKAGE_NAME}_BINARY} PARENT_SCOPE) - MARK_AS_ADVANCED(${PACKAGE_NAME}_BINARY) - - ENDIF(PACKAGE_BINARY) - - # Find our version if we can - IF((PACKAGE_VERSION_FILE AND PACKAGE_HEADER) OR (PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY)) - UNSET(full_version_string) - - # Read our package version file into a variable - IF(PACKAGE_VERSION_FILE AND PACKAGE_HEADER) - FILE(READ "${${PACKAGE_NAME}_INCLUDE_DIR}/${PACKAGE_VERSION_FILE}" full_version_string) - ENDIF(PACKAGE_VERSION_FILE AND PACKAGE_HEADER) - - # Execute our binary to get a version string - IF(PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY) - EXEC_PROGRAM(${${PACKAGE_NAME}_BINARY} - ARGS ${PACKAGE_VERSION_BINARY_ARGUMENTS} - OUTPUT_VARIABLE full_version_string) - ENDIF(PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY) - - # Build up our version string - SET(${PACKAGE_NAME}_VERSION "") - FOREACH(regex ${PACKAGE_VERSION_REGEX}) - STRING(REGEX REPLACE ".*${regex}.*" "\\1" regex_output ${full_version_string}) - SET(${PACKAGE_NAME}_VERSION ${${PACKAGE_NAME}_VERSION} ${regex_output}) - ENDFOREACH(regex) - STRING(REPLACE ";" "." ${PACKAGE_NAME}_VERSION "${${PACKAGE_NAME}_VERSION}") - - ENDIF((PACKAGE_VERSION_FILE AND PACKAGE_HEADER) OR (PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY)) - - INCLUDE(FindPackageHandleStandardArgs) - FIND_PACKAGE_HANDLE_STANDARD_ARGS(${PACKAGE_NAME} - FOUND_VAR ${PACKAGE_NAME}_FOUND - REQUIRED_VARS ${required_vars} - VERSION_VAR ${PACKAGE_NAME}_VERSION - #VERSION_VAR "${MAJOR}.${MINOR}.${PATCH}") +include(CMakeParseArguments) +function(ToolchainLibraryFinder) + + # Extract the arguments from our function call + set(options, "") + set(oneValueArgs "NAME") + set(multiValueArgs + "HEADER" + "LIBRARY" + "LIBRARIES" + "PATH_SUFFIX" + "BINARY" + "VERSION_FILE" + "VERSION_BINARY_ARGUMENTS" + "VERSION_REGEX" + "LINK_TYPE" + ) + cmake_parse_arguments(PACKAGE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Clear our required_vars variable + unset(required_vars) + + if(PACKAGE_LIBRARY OR PACKAGE_LIBRARIES) + if(PACKAGE_LINK_TYPE) + set(${PACKAGE_NAME}_LINK_TYPE + ${PACKAGE_LINK_TYPE} + CACHE STRING "Choose method to link the library" + ) + else() + set(${PACKAGE_NAME}_LINK_TYPE + UNKNOWN + CACHE STRING "Choose method to link the library" + ) + endif() + set_property(CACHE ${PACKAGE_NAME}_LINK_TYPE PROPERTY STRINGS "SHARED" "STATIC" "MODULE" "UNKNOWN") + mark_as_advanced(${PACKAGE_NAME}_LINK_TYPE) + + # Search only for specified libraries + if(${PACKAGE_NAME}_LINK_TYPE STREQUAL "STATIC") + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + # Uncache the incorrect value + if(${PACKAGE_NAME}_LIBRARY MATCHES ".*\.so$") + unset(${PACKAGE_NAME}_LIBRARY CACHE) + endif() + elseif(${PACKAGE_NAME}_LINK_TYPE STREQUAL "SHARED") + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX}) + # Uncache the incorrect value + if(${PACKAGE_NAME}_LIBRARY MATCHES ".*\.a$") + unset(${PACKAGE_NAME}_LIBRARY CACHE) + endif() + endif() + endif() + + # Find our library from the named library files + if(PACKAGE_LIBRARY) + find_library( + "${PACKAGE_NAME}_LIBRARY" + NAMES ${PACKAGE_LIBRARY} + PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} + DOC "The ${PACKAGE_NAME} (${PACKAGE_LIBRARY}) library" ) - # Export our found variable to parent scope - SET(${PACKAGE_NAME}_FOUND ${PACKAGE_NAME}_FOUND PARENT_SCOPE) + # Setup an imported target for this library + add_library(${PACKAGE_NAME}::${PACKAGE_NAME} ${${PACKAGE_NAME}_LINK_TYPE} IMPORTED) + set_target_properties(${PACKAGE_NAME}::${PACKAGE_NAME} PROPERTIES IMPORTED_LOCATION ${${PACKAGE_NAME}_LIBRARY}) -ENDFUNCTION(ToolchainLibraryFinder) + # Setup and export our variables + list(APPEND required_vars "${PACKAGE_NAME}_LIBRARY") + set(${PACKAGE_NAME}_LIBRARIES + ${${PACKAGE_NAME}_LIBRARY} + PARENT_SCOPE + ) + mark_as_advanced(${PACKAGE_NAME}_LIBRARY ${PACKAGE_NAME}_LIBRARIES) + + elseif(PACKAGE_LIBRARIES) + foreach(lib ${PACKAGE_LIBRARIES}) + find_library( + "${PACKAGE_NAME}_${lib}_LIBRARY" + NAMES ${lib} + PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} + DOC "The ${PACKAGE_NAME} (${lib}) library" + ) + + # Setup an imported target for this library + add_library(${PACKAGE_NAME}::${lib} ${${PACKAGE_NAME}_LINK_TYPE} IMPORTED) + set_target_properties(${PACKAGE_NAME}::${lib} PROPERTIES IMPORTED_LOCATION ${${PACKAGE_NAME}_${lib}_LIBRARY}) + + # Setup and export our variables + set(required_vars ${required_vars} "${PACKAGE_NAME}_${lib}_LIBRARY") + list(APPEND ${PACKAGE_NAME}_LIBRARIES ${PACKAGE_NAME}::${lib}) + mark_as_advanced(${PACKAGE_NAME}_${lib}_LIBRARY) + endforeach(lib ${PACKAGE_LIBRARIES}) + + # Link all of our imported targets to our imported library + add_library(${PACKAGE_NAME}::${PACKAGE_NAME} INTERFACE IMPORTED) + target_link_libraries(${PACKAGE_NAME}::${PACKAGE_NAME} INTERFACE ${${PACKAGE_NAME}_LIBRARIES}) + + # Make sure the libraries exist in the parent scope + set(${PACKAGE_NAME}_LIBRARIES + ${${PACKAGE_NAME}_LIBRARIES} + PARENT_SCOPE + ) + mark_as_advanced(${PACKAGE_NAME}_LIBRARIES) + endif() + + # Find our include path from our named headers + if(PACKAGE_HEADER) + + # Find our include path + find_path( + "${PACKAGE_NAME}_INCLUDE_DIR" + NAMES ${PACKAGE_HEADER} + DOC "The ${PACKAGE_NAME} (${PACKAGE_LIBRARY}) include directory" + PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} + ) + + # Add include directories to our imported library + target_include_directories(${PACKAGE_NAME}::${PACKAGE_NAME} SYSTEM INTERFACE ${${PACKAGE_NAME}_INCLUDE_DIR}) + + # Setup and export our variables + list(APPEND required_vars "${PACKAGE_NAME}_INCLUDE_DIR") + set(${PACKAGE_NAME}_INCLUDE_DIRS + ${${PACKAGE_NAME}_INCLUDE_DIR} + PARENT_SCOPE + ) + mark_as_advanced(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) + + endif() + + # Find our binary from the named binary files + if(PACKAGE_BINARY) + find_program( + "${PACKAGE_NAME}_BINARY" + NAMES ${PACKAGE_BINARY} + PATH_SUFFIXES ${PACKAGE_PATH_SUFFIX} + DOC "The ${PACKAGE_NAME} (${PACKAGE_BINARY}) executable program" + ) + + # Created an imported executable + add_executable(${PACKAGE_NAME}::${PACKAGE_BINARY} IMPORTED GLOBAL) + set_target_properties(${PACKAGE_NAME}::${PACKAGE_BINARY} PROPERTIES IMPORTED_LOCATION ${${PACKAGE_NAME}_BINARY}) + + # Setup and export our variables + list(APPEND required_vars "${PACKAGE_NAME}_BINARY") + set(${PACKAGE_NAME}_BINARY + ${${PACKAGE_NAME}_BINARY} + PARENT_SCOPE + ) + mark_as_advanced(${PACKAGE_NAME}_BINARY) + + endif() + + # Find our version if we can + if((PACKAGE_VERSION_FILE AND PACKAGE_HEADER) OR (PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY)) + unset(full_version_string) + + # Read our package version file into a variable + if(PACKAGE_VERSION_FILE AND PACKAGE_HEADER) + file(READ "${${PACKAGE_NAME}_INCLUDE_DIR}/${PACKAGE_VERSION_FILE}" full_version_string) + endif() + + # Execute our binary to get a version string + if(PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY) + exec_program( + ${${PACKAGE_NAME}_BINARY} ARGS + ${PACKAGE_VERSION_BINARY_ARGUMENTS} + OUTPUT_VARIABLE full_version_string + ) + endif() + + # Build up our version string + set(${PACKAGE_NAME}_VERSION "") + foreach(regex ${PACKAGE_VERSION_REGEX}) + string(REGEX REPLACE ".*${regex}.*" "\\1" regex_output ${full_version_string}) + set(${PACKAGE_NAME}_VERSION ${${PACKAGE_NAME}_VERSION} ${regex_output}) + endforeach(regex) + string(REPLACE ";" "." ${PACKAGE_NAME}_VERSION "${${PACKAGE_NAME}_VERSION}") + + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args( + ${PACKAGE_NAME} + FOUND_VAR ${PACKAGE_NAME}_FOUND + REQUIRED_VARS ${required_vars} + VERSION_VAR ${PACKAGE_NAME}_VERSION # VERSION_VAR "${MAJOR}.${MINOR}.${PATCH}") + ) + + # Export our found variable to parent scope + set(${PACKAGE_NAME}_FOUND + ${PACKAGE_NAME}_FOUND + PARENT_SCOPE + ) + +endfunction(ToolchainLibraryFinder) diff --git a/cmake/Scripts/build_message_class.py b/cmake/Scripts/build_message_class.py new file mode 100755 index 0000000..b380544 --- /dev/null +++ b/cmake/Scripts/build_message_class.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# MIT License +# +# Copyright (c) 2016 NUbots +# +# This file is part of the NUbots codebase. +# See https://github.com/NUbots/NUbots for further info. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +import sys + +import generator.File +from google.protobuf.descriptor_pb2 import FileDescriptorSet + +base_file = sys.argv[1] + +with open("{}.pb".format(base_file), "rb") as f: + # Load the descriptor protobuf file + d = FileDescriptorSet() + d.ParseFromString(f.read()) + + # Check that there is only one file + assert len(d.file) == 1 + + # Load the file + b = generator.File.File(d.file[0], base_file) + + # Generate the c++ file + header, impl, python = b.generate_cpp() + + with open("{}.hpp".format(base_file), "w") as f: + f.write(header) + + with open("{}.cpp".format(base_file), "w") as f: + f.write(impl) + + with open("{}.py.cpp".format(base_file), "w") as f: + f.write(python) diff --git a/cmake/Scripts/build_message_reflection.py b/cmake/Scripts/build_message_reflection.py new file mode 100644 index 0000000..e867c41 --- /dev/null +++ b/cmake/Scripts/build_message_reflection.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# +# MIT License +# +# Copyright (c) 2021 NUbots +# +# This file is part of the NUbots codebase. +# See https://github.com/NUbots/NUbots for further info. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import os +import pkgutil +import sys + +import google.protobuf.message +import xxhash +from generator.textutil import dedent, indent + +if __name__ == "__main__": + + python_message_root = sys.argv[1] + reflection_output_header = sys.argv[2] + + # Load all our protocol buffer files as modules into this file + includes = [] + sys.path.append(python_message_root) + for dir_name, subdir, files in os.walk(python_message_root): + modules = pkgutil.iter_modules(path=[dir_name]) + for loader, module_name, ispkg in modules: + if module_name.endswith("pb2"): + + # Work out what header file this came from + include = os.path.join( + os.path.relpath(dir_name, python_message_root), "{}.hpp".format(module_name[:-4]) + ) + + # If it's one of ours include it + if include.startswith("message"): + includes.append(include) + + # Load our protobuf module + fqdn = os.path.normpath( + os.path.join(os.path.relpath(dir_name, python_message_root), module_name) + ).replace(os.sep, ".") + if fqdn not in sys.modules: + loader.find_module(fqdn).load_module(fqdn) + + # Now that we've imported them all get all the subclasses of protobuf message + messages = set() + for message in google.protobuf.message.Message.__subclasses__(): + + # Work out our original protobuf type + pb_type = ".".join(message.DESCRIPTOR.full_name.split(".")[1:]) + + # Only include our own messages + if pb_type.startswith("message.") and not message.DESCRIPTOR.GetOptions().map_entry: + messages.add(pb_type) + + messages = list(messages) + + includes = "\n".join('#include "{}"'.format(i) for i in includes) + + cases_reflect = "\n".join( + [ + "case 0x{}: return std::make_unique>();".format( + xxhash.xxh64(m, seed=0x4E55436C).hexdigest(), "::".join(m.split(".")) + ) + for m in messages + ] + ) + + cases_trait = "\n".join( + [ + "case 0x{}: return TypeTrait<{}>::value;".format( + xxhash.xxh64(m, seed=0x4E55436C).hexdigest(), "::".join(m.split(".")) + ) + for m in messages + ] + ) + + output = dedent( + """\ + #ifndef MESSAGE_REFLECTION_HPP + #define MESSAGE_REFLECTION_HPP + + #include + #include + #include + #include + + #include "utility/reflection/reflection_exceptions.hpp" + #include "utility/type_traits/has_id.hpp" + + {includes} + + namespace message::reflection {{ + using utility::reflection::unknown_message; + + template