diff --git a/cmake/Platform/Arduino.cmake b/cmake/Platform/Arduino.cmake index 4e77841..abe748b 100644 --- a/cmake/Platform/Arduino.cmake +++ b/cmake/Platform/Arduino.cmake @@ -6,13 +6,10 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Other) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Properties) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Sketches) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Sources) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Libraries) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/Targets) -include(MathUtils) -include(ListUtils) -include(StringUtils) -include(PropertyUtils) -include(PlatformLibraryUtils) +include(Utilities) include(BoardManager) include(RecipeParser) @@ -20,6 +17,9 @@ include(TargetFlagsManager) include(SourcesManager) include(SketchManager) include(DefaultsManager) +include(ArchitectureSupportQuery) + +include(Libraries) include(BuildSystemInitializer) diff --git a/cmake/Platform/Libraries/Libraries.cmake b/cmake/Platform/Libraries/Libraries.cmake new file mode 100644 index 0000000..ff0a7e2 --- /dev/null +++ b/cmake/Platform/Libraries/Libraries.cmake @@ -0,0 +1,4 @@ +include(LibraryArchitectureParser) +include(LibrarySourcesArchitectureResolver) +include(LibraryFlagsManager) +include(LibrariesFinder) diff --git a/cmake/Platform/Libraries/LibrariesFinder.cmake b/cmake/Platform/Libraries/LibrariesFinder.cmake new file mode 100644 index 0000000..5b648b2 --- /dev/null +++ b/cmake/Platform/Libraries/LibrariesFinder.cmake @@ -0,0 +1,64 @@ +#=============================================================================# +# Finds an Arduino library with the given library name and creates a library target from it +# with the given target name. +# The search process also resolves library's architecture to check if it even can be built +# using the current platform architecture. +# _target_name - Name of the library target to be created. Usually library's real name. +# _library_name - Name of the Arduino library to find. +# _board_id - Board ID associated with the linked Core Lib. +# [3RD_PARTY] - Whether library should be treated as a 3rd Party library. +#=============================================================================# +function(find_arduino_library _target_name _library_name _board_id) + + set(argument_options "3RD_PARTY" "HEADER_ONLY") + cmake_parse_arguments(parsed_args "${argument_options}" "" "" ${ARGN}) + + if (NOT parsed_args_3RD_PARTY) + convert_string_to_pascal_case(${_library_name} _library_name) + endif () + + find_file(library_properties_file library.properties + PATHS ${ARDUINO_SDK_LIBRARIES_PATH} ${ARDUINO_CMAKE_SKETCHBOOK_PATH}/libraries + PATH_SUFFIXES ${_library_name} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH) + + if (${library_properties_file} MATCHES "NOTFOUND") + message(SEND_ERROR "Couldn't find library named ${_library_name}") + else () # Library is found + + get_filename_component(library_path ${library_properties_file} DIRECTORY) + + find_library_header_files("${library_path}" library_headers) + + if (NOT library_headers) + message(SEND_ERROR "Couldn't find any header files for the ${_library_name} library") + else () + + if (parsed_args_HEADER_ONLY) + add_arduino_header_only_library(${_target_name} ${_board_id} ${library_headers}) + + else () + + find_library_source_files("${library_path}" library_sources) + + if (NOT library_sources) + message(SEND_ERROR "Couldn't find any source files for the " + "${_library_name} library - Is it a header-only library?" + "If so, please pass the HEADER_ONLY option " + "as an argument to the function") + else () + set(sources ${library_headers} ${library_sources}) + + add_arduino_library(${_target_name} ${_board_id} + LIB_PROPS_FILE ${library_properties_file} + ${sources}) + endif () + + endif () + endif () + endif () + + unset(library_properties_file CACHE) + +endfunction() diff --git a/cmake/Platform/Libraries/LibraryArchitectureParser.cmake b/cmake/Platform/Libraries/LibraryArchitectureParser.cmake new file mode 100644 index 0000000..b16a138 --- /dev/null +++ b/cmake/Platform/Libraries/LibraryArchitectureParser.cmake @@ -0,0 +1,22 @@ +#=============================================================================# +# Gets a list of architectures supported by a library. +# The list is read from the given properties file, which includes metadata about the library. +# _library_properties_file - Full path to a library's properties file +# (usually named 'library,propertie'). +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - List of architectures supported by the library or '*' +# if it's architecture-agnostic (Supports all arhcitectures). +#=============================================================================# +function(get_arduino_library_supported_architectures _library_properties_file _return_var) + + file(STRINGS ${_library_properties_file} library_properties) + + list(FILTER library_properties INCLUDE REGEX "arch") + + _get_property_value("${library_properties}" _library_arch_list) + + string(REPLACE "," ";" _library_arch_list ${_library_arch_list}) # Turn into a valid list + + set(${_return_var} ${_library_arch_list} PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Libraries/LibraryFlagsManager.cmake b/cmake/Platform/Libraries/LibraryFlagsManager.cmake new file mode 100644 index 0000000..a7d2964 --- /dev/null +++ b/cmake/Platform/Libraries/LibraryFlagsManager.cmake @@ -0,0 +1,19 @@ +#=============================================================================# +# Sets compiler and linker flags on the given library target. +# Changes are kept even outside the scope of the function since they apply on a target. +# _library_target - Name of the library target. +# _board_id - Board ID associated with the library. Some flags require it. +#=============================================================================# +function(set_library_flags _library_target _board_id) + + parse_scope_argument(scope "${ARGN}" + DEFAULT_SCOPE PUBLIC) + + # Set C++ compiler flags + get_cmake_compliant_language_name(cpp flags_language) + set_compiler_target_flags(${_library_target} "${_board_id}" ${scope} LANGUAGE ${flags_language}) + + # Set linker flags + set_linker_flags(${_library_target} "${_board_id}") + +endfunction() diff --git a/cmake/Platform/Libraries/LibrarySourcesArchitectureResolver.cmake b/cmake/Platform/Libraries/LibrarySourcesArchitectureResolver.cmake new file mode 100644 index 0000000..335a1b9 --- /dev/null +++ b/cmake/Platform/Libraries/LibrarySourcesArchitectureResolver.cmake @@ -0,0 +1,66 @@ +#=============================================================================# +# Filters sources that relate to an architecture from the given list of unsupported architectures. +# _unsupported_archs_regex - List of unsupported architectures as a regex-pattern string. +# _sources - List of sources to check and potentially filter. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - Filtered list of sources containing only those that don't relate to +# any unsupported architecture. +#=============================================================================# +function(_filter_unsupported_arch_sources _unsupported_archs_regex _sources _return_var) + + if (NOT "${_unsupported_archs_regex}" STREQUAL "") # Not all architectures are supported + # Filter sources dependant on unsupported architectures + list(FILTER _sources EXCLUDE REGEX ${_unsupported_archs_regex}) + endif () + + set(${_return_var} ${_sources} PARENT_SCOPE) + +endfunction() + +#=============================================================================# +# Resolves library's architecture-related elements by doing several things: +# 1. Checking whether the platform's architecture is supported by the library +# 2. Filtering out any library sources that relate to unsupported architectures, i.e +# architectures other than the platform's. +# If the platform's architecture isn't supported by the library, CMake generates an error and stops. +# _library_sources - List of library's sources to check and potentially filter. +# [LIB_PROPS_FILE] - Full path to the library's properties file. Optional. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - Filtered list of sources containing only those that don't relate to +# any unsupported architecture. +#=============================================================================# +function(resolve_library_architecture _library_sources _return_var) + + cmake_parse_arguments(parsed_args "" "LIB_PROPS_FILE" "" ${ARGN}) + + if (parsed_args_LIB_PROPS_FILE) # Library properties file is given + set(lib_props_file ${parsed_args_LIB_PROPS_FILE}) + else () + + # Warn user and assume library is arch-agnostic + message(STATUS "Library's properties file can't be found " + "under its' root directory - Assuming the library " + "is architecture-agnostic (supports all architectures)") + set(${_return_var} "${_library_sources}" PARENT_SCOPE) + return() + + endif () + + get_arduino_library_supported_architectures("${lib_props_file}" lib_archs) + + # Check if the platform's architecture is supported by the library + is_platform_architecture_supported(${lib_archs} arch_supported_by_lib) + + if (NOT ${arch_supported_by_lib}) + message(SEND_ERROR "The platform's architecture, ${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}, " + "isn't supported by the ${_library_name} library") + endif () + + get_unsupported_architectures("${lib_archs}" unsupported_archs REGEX) + + # Filter any sources that aren't supported by the platform's architecture + _filter_unsupported_arch_sources("${unsupported_archs}" "${_library_sources}" valid_sources) + + set(${_return_var} "${valid_sources}" PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Other/ArchitectureSupportQuery.cmake b/cmake/Platform/Other/ArchitectureSupportQuery.cmake new file mode 100644 index 0000000..7897730 --- /dev/null +++ b/cmake/Platform/Other/ArchitectureSupportQuery.cmake @@ -0,0 +1,64 @@ +#=============================================================================# +# Checks whether the platform's architecture is supported in the context of the given list +# of architectures, i.e If the list contains the platform's architecture. +# _arch_list - List of architectures supported by the library, +# inferred from its' 'library.properties' file. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - True if supported, false otherwise. +#=============================================================================# +function(is_platform_architecture_supported _arch_list _return_var) + + if ("${_arch_list}" MATCHES "\\*") # Any architecture is supported + set(result TRUE) + else () + if (${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE} IN_LIST _arch_list) + set(result TRUE) # Our platform's arch is supported + else () + set(result FALSE) # Our arch isn't supported + endif () + endif () + + set(${_return_var} ${result} PARENT_SCOPE) + +endfunction() + +#=============================================================================# +# Gets a filtered list of architectures that aren't compliant with the platform's architecture. +# e.g If a list contains 'avr' and 'nrf52', while our arch is 'avr', 'nrf52' will be returned. +# _arch_list - List of all architectures probably read from a library's properties file +# [REGEX] - Returns list in a regex-compatible mode, allowing caller to +# use result in search patterns. This is currently the only supported mode. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - Filtered list of architectures. +#=============================================================================# +function(get_unsupported_architectures _arch_list _return_var) + + cmake_parse_arguments(parsed_args "REGEX" "" "" ${ARGN}) + + if ("${_arch_list}" MATCHES "\\*") # All architectures are supported, return nothing + return() + endif () + + list(FILTER _arch_list EXCLUDE REGEX ${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}) + set(unsupported_arch_list ${_arch_list}) # Just for better readability + + if (parsed_args_REGEX) # Return in regex format + + foreach (arch ${unsupported_arch_list}) + # Append every unsupported-architecture and "|" to represent "or" in regex-fomart + string(APPEND unsupported_archs_regex "${arch}" "|") + endforeach () + + # Remove last "|" as it's unnecessary - There's no element after it + string(LENGTH ${unsupported_archs_regex} str_len) + decrement_integer(str_len 1) # Decrement string's length by 1 to trim last char ('|') + string(SUBSTRING ${unsupported_archs_regex} 0 ${str_len} unsupported_archs_regex) + + # prepare for generalized function return + set(unsupported_arch_list ${unsupported_archs_regex}) + + endif () + + set(${_return_var} ${unsupported_arch_list} PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Other/TargetFlagsManager.cmake b/cmake/Platform/Other/TargetFlagsManager.cmake index 0181d8e..90577bc 100644 --- a/cmake/Platform/Other/TargetFlagsManager.cmake +++ b/cmake/Platform/Other/TargetFlagsManager.cmake @@ -5,25 +5,24 @@ #=============================================================================# function(set_compiler_target_flags _target_name _board_id) - set(option_args PRIVATE PUBLIC INTERFACE) - set(single_args LANGUAGE) - cmake_parse_arguments(compiler "${option_args}" "${single_args}" "" ${ARGN}) - - if (compiler_LANGUAGE) - if (compiler_PRIVATE) - set(scope PRIVATE) - elseif (compiler_INTERFACE) - set(scope INTERFACE) - else () - set(scope PUBLIC) - endif () + cmake_parse_arguments(parsed_args "" "LANGUAGE" "" ${ARGN}) + parse_scope_argument(scope "${ARGN}" + DEFAULT_SCOPE PUBLIC) + + if (parsed_args_LANGUAGE) + parse_compiler_recipe_flags("${_board_id}" compiler_recipe_flags - LANGUAGE "${compiler_LANGUAGE}") + LANGUAGE "${parsed_args_LANGUAGE}") + target_compile_options(${_target_name} ${scope} - $<$:${compiler_recipe_flags}>) + $<$:${compiler_recipe_flags}>) + else () + parse_compiler_recipe_flags("${_board_id}" compiler_recipe_flags) - target_compile_options(${_target_name} PUBLIC ${compiler_recipe_flags}) + + target_compile_options(${_target_name} ${scope} ${compiler_recipe_flags}) + endif () endfunction() @@ -36,7 +35,9 @@ endfunction() function(set_linker_flags _target_name _board_id) parse_linker_recpie_pattern("${_board_id}" linker_recipe_flags) + string(REPLACE ";" " " cmake_compliant_linker_flags "${linker_recipe_flags}") + set(CMAKE_EXE_LINKER_FLAGS "${cmake_compliant_linker_flags}" CACHE STRING "" FORCE) endfunction() @@ -81,3 +82,18 @@ function(set_upload_target_flags _target_name _board_id _upload_port _return_var endfunction() +#=============================================================================# +# Adds a compiler definition (#define) for the given architecture to the target. +# The affecting scope of the definition is controlled by the _scope argument. +# _target - Name of the target (Executable) to set flags on. +# _scope - PUBLIC|INTERFACE|PRIVATE. Affects outer scope - How other targets see it. +# _architecture - Architecture to define, e.g. 'avr' +#=============================================================================# +function(set_target_architecture_definition _target _scope _architecture) + + string(TOUPPER ${_architecture} upper_arch) + set(arch_definition "ARDUINO_ARCH_${upper_arch}") + + target_compile_definitions(${_target} ${_scope} ${arch_definition}) + +endfunction() diff --git a/cmake/Platform/System/DefaultsManager.cmake b/cmake/Platform/System/DefaultsManager.cmake index 27de7fc..8200144 100644 --- a/cmake/Platform/System/DefaultsManager.cmake +++ b/cmake/Platform/System/DefaultsManager.cmake @@ -57,6 +57,13 @@ function(set_default_arduino_cmake_options) endfunction() +function(set_default_paths) + + set(ARDUINO_CMAKE_LIBRARY_PROPERTIES_FILE_NAME "library.properties" CACHE STRING + "Name of the libraries' properties file") + +endfunction() + #=============================================================================# # Sets various defaults used throughout the platform. #=============================================================================# @@ -65,5 +72,6 @@ function(set_arduino_cmake_defaults) set_internal_search_patterns() set_source_files_patterns() set_default_arduino_cmake_options() + set_default_paths() endfunction() diff --git a/cmake/Platform/Targets/ArduinoCMakeLibraryTarget.cmake b/cmake/Platform/Targets/ArduinoCMakeLibraryTarget.cmake index 23635b0..3f9a8b6 100644 --- a/cmake/Platform/Targets/ArduinoCMakeLibraryTarget.cmake +++ b/cmake/Platform/Targets/ArduinoCMakeLibraryTarget.cmake @@ -1,31 +1,3 @@ -#=============================================================================# -# Sets compiler and linker flags on the given library target. -# Changes are kept even outside the scope of the function since they apply on a target. -# _library_target - Name of the library target. -# _board_id - Board ID associated with the library. Some flags require it. -#=============================================================================# -function(_set_library_flags _library_target _board_id) - - set(scope_options "PRIVATE" "PUBLIC" "INTERFACE") - cmake_parse_arguments(parsed_args "${scope_options}" "" "" ${ARGN}) - - if (parsed_args_PRIVATE) - set(scope PRIVATE) - elseif (parsed_args_INTERFACE) - set(scope INTERFACE) - else () - set(scope PUBLIC) - endif () - - # Set C++ compiler flags - get_cmake_compliant_language_name(cpp flags_language) - set_compiler_target_flags(${_library_target} "${_board_id}" ${scope} LANGUAGE ${flags_language}) - - # Set linker flags - set_linker_flags(${_library_target} "${_board_id}") - -endfunction() - #=============================================================================# # Creates a library target compliant with the Arduino library standard. # One can also specify an architecture for the library, which will result in a special parsing @@ -38,21 +10,7 @@ endfunction() #=============================================================================# function(_add_arduino_cmake_library _target_name _board_id _sources) - cmake_parse_arguments(parsed_args "INTERFACE" "ARCH" "" ${ARGN}) - - if (parsed_args_ARCH) # Treat architecture-specific libraries differently - # Filter any sources that aren't supported by the platform's architecture - list(LENGTH library_ARCH num_of_libs_archs) - if (${num_of_libs_archs} GREATER 1) - # Exclude all unsupported architectures, request filter in regex mode - _get_unsupported_architectures("${parsed_args_ARCH}" arch_filter REGEX) - set(filter_type EXCLUDE) - else () - set(arch_filter "src\\/[^/]+\\.|${parsed_args_ARCH}") - set(filter_type INCLUDE) - endif () - list(FILTER _sources ${filter_type} REGEX ${arch_filter}) - endif () + cmake_parse_arguments(parsed_args "INTERFACE" "" "" ${ARGN}) if (parsed_args_INTERFACE) add_library(${_target_name} INTERFACE) @@ -66,13 +24,10 @@ function(_add_arduino_cmake_library _target_name _board_id _sources) get_headers_parent_directories("${_sources}" include_dirs) target_include_directories(${_target_name} ${scope} ${include_dirs}) - _set_library_flags(${_target_name} ${_board_id} ${scope}) + set_library_flags(${_target_name} ${_board_id} ${scope}) - if (parsed_args_ARCH) - string(TOUPPER ${parsed_args_ARCH} upper_arch) - set(arch_definition "ARDUINO_ARCH_${upper_arch}") - target_compile_definitions(${_target_name} ${scope} ${arch_definition}) - endif () + set_target_architecture_definition(${_target_name} ${scope} + ${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}) endfunction() @@ -82,7 +37,7 @@ endfunction() # then links it to the library. # _target_name - Name of the target to link against. # _library_name - Name of the library target to link. -# [PRIVATE|PUBLIC|INTERFACE] - Optional link scope. +# [PRIVATE|PUBLIC|INTERFACE] - Optional link scope for the internally linked Core-Lib. # [BOARD_CORE_TARGET] - Optional target name of the Core Lib to use. # Use when the target is a library. #=============================================================================# @@ -92,29 +47,28 @@ function(_link_arduino_cmake_library _target_name _library_name) message(FATAL_ERROR "Target doesn't exist - It must be created first!") endif () - set(scope_options "PRIVATE" "PUBLIC" "INTERFACE") - cmake_parse_arguments(link_library "${scope_options}" "BOARD_CORE_TARGET" "" ${ARGN}) + cmake_parse_arguments(parsed_args "" "BOARD_CORE_TARGET" "" ${ARGN}) + parse_scope_argument(scope "${ARGN}") - # Now, link library to executable - if (link_library_PUBLIC) - set(scope PUBLIC) - elseif (link_library_INTERFACE) - set(scope INTERFACE) - else () - set(scope PRIVATE) - endif () - - # First, include core lib's directories in library as well - if (link_library_BOARD_CORE_TARGET) - set(core_target ${link_library_BOARD_CORE_TARGET}) + # Resolve Core-Lib's target + if (parsed_args_BOARD_CORE_TARGET) + set(core_target ${parsed_args_BOARD_CORE_TARGET}) else () set(core_target ${${_target_name}_CORE_LIB_TARGET}) endif () get_target_property(core_lib_includes ${core_target} INCLUDE_DIRECTORIES) + + # Include core lib's include directories in library target, then link to it target_include_directories(${_library_name} ${scope} "${core_lib_includes}") target_link_libraries(${_library_name} ${scope} ${core_target}) - target_link_libraries(${_target_name} PRIVATE ${_library_name}) + # Link library target to linked-to target + if (parsed_args_PRIVATE) + target_link_libraries(${_target_name} PRIVATE ${_library_name}) + else () + # Link 'INTERFACE' targets publicly, otherwise code won't compile + target_link_libraries(${_target_name} PUBLIC ${_library_name}) + endif () endfunction() diff --git a/cmake/Platform/Targets/ArduinoLibraryTarget.cmake b/cmake/Platform/Targets/ArduinoLibraryTarget.cmake index cb78c1c..8115ad8 100644 --- a/cmake/Platform/Targets/ArduinoLibraryTarget.cmake +++ b/cmake/Platform/Targets/ArduinoLibraryTarget.cmake @@ -1,168 +1,55 @@ #=============================================================================# -# Gets the library architecure if any, read from the given library properties file -# which includes this information. -# _library_properties_file - Full path to a library's properties file. -# _return_var - Name of variable in parent-scope holding the return value. -# Returns - If library is architecure neutral, nothing is returned. -# If library doesn't support the current platform's architecture, -# "UNSUPPORTED" string is returned. -# Otherwise, the platform's architecture is returned. +# Creates a library target for the given name and sources. +# As it's an Arduino library, it also finds and links all dependent platform libraries (if any). +# _target_name - Name of the library target to be created. Usually library's real name. +# _board_id - Board ID associated with the linked Core Lib. +# [LIB_PROPS_FILE] - Full path to the library's properties file. Optional. +# [Sources] - List of source files (Could also be headers for code-inspection in some IDEs) +# to create the executable from, similar to CMake's built-in add_executable. #=============================================================================# -function(_get_library_architecture _library_properties_file _return_var) - - file(STRINGS ${_library_properties_file} library_properties) +function(add_arduino_library _target_name _board_id) - list(FILTER library_properties INCLUDE REGEX "arch") - _get_property_value("${library_properties}" arch_list) - string(REPLACE "," ";" arch_list ${arch_list}) # Turn into a valid list + cmake_parse_arguments(parsed_args "" "LIB_PROPS_FILE" "" ${ARGN}) + parse_sources_arguments(parsed_sources "" "LIB_PROPS_FILE" "" "${ARGN}") - if ("${arch_list}" MATCHES "\\*") - return() # Any architecture is supported, return nothing + if (parsed_args_LIB_PROPS_FILE) + resolve_library_architecture("${parsed_sources}" arch_resolved_sources + LIB_PROPS_FILE ${parsed_args_LIB_PROPS_FILE}) else () - list(LENGTH arch_list num_of_supported_archs) - if (${num_of_supported_archs} GREATER 1) # Number of architectures is supported - list(FIND arch_list ${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE} platform_arch_index) - if (${platform_arch_index} LESS 0) # Our arch isn't supported - set(__arch "UNSUPPORTED") - else () # Our arch is indeed supported - set(__arch ${arch_list}) - endif () - else () - list(GET arch_list 0 __arch) - if (NOT "${__arch}" STREQUAL "${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}") - set(__arch "UNSUPPORTED") # Our arch isn't supported - endif () - endif () - endif () - set(${_return_var} ${__arch} PARENT_SCOPE) + get_sources_root_directory("${parsed_sources}" library_root_dir) -endfunction() + get_library_properties_file(${library_root_dir} library_properties_file) + + if (library_properties_file) # Properties file has been found + resolve_library_architecture("${parsed_sources}" arch_resolved_sources + LIB_PROPS_FILE ${library_properties_file}) + else () + resolve_library_architecture("${parsed_sources}" arch_resolved_sources) + endif () -#=============================================================================# -# Gets a filtered list of architectures that aren't compliant with the platform's architecture. -# For example: If a list contains 'avr' and 'nrf52', while our arch is 'avr', 'nrf52' will be returned. -# _arch_list - List of all architectures probably read from a library's properties file -# _return_var - Name of variable in parent-scope holding the return value. -# Returns - Filtered list of architectures. -#=============================================================================# -function(_get_unsupported_architectures _arch_list _return_var) - - cmake_parse_arguments(unsupported_archs "REGEX" "" "" ${ARGN}) - - list(FILTER _arch_list EXCLUDE REGEX - "${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}") - if (unsupported_archs_REGEX) # Return in regex format - list(LENGTH _arch_list num_of_unsupported_archs) - set(unsupported_arch_list "") - set(arch_index 1) - foreach (unsupported_arch ${_arch_list}) - string(APPEND unsupported_arch_list "${unsupported_arch}") - if (${arch_index} LESS ${num_of_unsupported_archs}) - string(APPEND unsupported_arch_list "|") - endif () - increment_integer(arch_index 1) - endforeach () endif () - set(${_return_var} ${unsupported_arch_list} PARENT_SCOPE) + _add_arduino_cmake_library(${_target_name} ${_board_id} "${arch_resolved_sources}") -endfunction() + find_dependent_platform_libraries("${arch_resolved_sources}" lib_platform_libs) -#=============================================================================# -# Creates a library target for the given name and sources. -# As it's an Arduino library, it also finds and links all dependent platform libraries (if any). -# _target_name - Name of the library target to be created. Usually library's real name. -# _board_id - Board ID associated with the linked Core Lib. -# _sources - Source and header files to create target from. -#=============================================================================# -function(add_arduino_library _target_name _board_id _sources) - - _add_arduino_cmake_library(${_target_name} ${_board_id} "${_sources}" "${ARGN}") - find_dependent_platform_libraries("${_sources}" lib_platform_libs) foreach (platform_lib ${lib_platform_libs}) link_platform_library(${_target_name} ${platform_lib} ${_board_id}) endforeach () endfunction() -function(add_arduino_header_only_library _target_name _board_id) - - cmake_parse_arguments(parsed_args "ARCH" "" "HEADERS" ${ARGN}) - - _add_arduino_cmake_library(${_target_name} ${_board_id} "${parsed_args_HEADERS}" - INTERFACE ${parsed_args_ARCH}) - -endfunction() - #=============================================================================# -# Finds an Arduino library with the given library name and creates a library target from it -# with the given target name. -# The search process also resolves library's architecture to check if it even can be built -# using the current platform architecture. -# _target_name - Name of the library target to be created. Usually library's real name. -# _library_name - Name of the Arduino library to find. +# Creates a header-only library target for the given name and sources. +# _target_name - Name of the "executable" target. # _board_id - Board ID associated with the linked Core Lib. -# [3RD_PARTY] - Whether library should be treated as a 3rd Party library. -# [HEADER_ONLY] - Whether library is a header-only library, i.e has no source files #=============================================================================# -function(find_arduino_library _target_name _library_name _board_id) - - set(argument_options "3RD_PARTY" "HEADER_ONLY") - cmake_parse_arguments(parsed_args "${argument_options}" "" "" ${ARGN}) - - if (NOT parsed_args_3RD_PARTY) - convert_string_to_pascal_case(${_library_name} _library_name) - endif () - - find_file(library_properties_file library.properties - PATHS ${ARDUINO_SDK_LIBRARIES_PATH} ${ARDUINO_CMAKE_SKETCHBOOK_PATH}/libraries - PATH_SUFFIXES ${_library_name} - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH) - - if (${library_properties_file} MATCHES "NOTFOUND") - message(SEND_ERROR "Couldn't find library named ${_library_name}") - else () # Library is found - get_filename_component(library_path ${library_properties_file} DIRECTORY) - _get_library_architecture("${library_properties_file}" lib_arch) - if (lib_arch) - if ("${lib_arch}" MATCHES "UNSUPPORTED") - string(CONCAT error_message - "${_library_name} " - "library isn't supported on the platform's architecture " - "${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}") - message(SEND_ERROR ${error_message}) - endif () - endif () +function(add_arduino_header_only_library _target_name _board_id) - find_library_header_files("${library_path}" library_headers) - if (NOT library_headers) - set(error_message "Couldn't find any header files for the ${_library_name} library") - message(SEND_ERROR "${error_message}") - else () - if (parsed_args_HEADER_ONLY) - add_arduino_header_only_library(${_target_name} ${_board_id} - ARCH ${lib_arch} - HEADERS ${library_headers}) - else () - find_library_source_files("${library_path}" library_sources) - if (NOT library_sources) - string(CONCAT error_message - "Couldn't find any source files for the ${_library_name} library - " - "Is it a header-only library?\n" - "If so, please pass the HEADER_ONLY option as an argument to the function") - message(SEND_ERROR "${error_message}") - else () - set(sources ${library_headers} ${library_sources}) - add_arduino_library(${_target_name} ${_board_id} "${sources}" - ARCH ${lib_arch}) - endif () - endif () - endif () - endif () + parse_sources_arguments(parsed_headers "" "" "" "${ARGN}") - unset(library_properties_file CACHE) + _add_arduino_cmake_library(${_target_name} ${_board_id} "${parsed_headers}" INTERFACE) endfunction() @@ -189,13 +76,13 @@ function(link_arduino_library _target_name _library_target_name _board_id) endif () if (parsed_args_HEADER_ONLY) - _link_arduino_cmake_library(${_target_name} ${_library_target_name} - INTERFACE - BOARD_CORE_TARGET ${core_lib_target}) + set(scope INTERFACE) else () - _link_arduino_cmake_library(${_target_name} ${_library_target_name} - PUBLIC - BOARD_CORE_TARGET ${core_lib_target}) + set(scope PUBLIC) endif () + _link_arduino_cmake_library(${_target_name} ${_library_target_name} + ${scope} + BOARD_CORE_TARGET ${core_lib_target}) + endfunction() diff --git a/cmake/Platform/Targets/PlatformLibraryTarget.cmake b/cmake/Platform/Targets/PlatformLibraryTarget.cmake index 3c01144..d14d702 100644 --- a/cmake/Platform/Targets/PlatformLibraryTarget.cmake +++ b/cmake/Platform/Targets/PlatformLibraryTarget.cmake @@ -11,9 +11,13 @@ function(find_dependent_platform_libraries _sources _return_var) get_source_file_included_headers(${source} source_includes WE) list(APPEND included_headers_names ${source_includes}) endforeach () - list(REMOVE_DUPLICATES included_headers_names) + + if (included_headers_names) + list(REMOVE_DUPLICATES included_headers_names) + endif () get_platform_libraries_from_names("${included_headers_names}" dependent_libs) + set(${_return_var} ${dependent_libs} PARENT_SCOPE) endfunction() @@ -29,8 +33,7 @@ function(_add_platform_library _library_name _board_id) find_source_files("${ARDUINO_CMAKE_PLATFORM_LIBRARIES_PATH}/${_library_name}/src" lib_source_files) set(lib_sources ${lib_headers} ${lib_source_files}) - _add_arduino_cmake_library(${_library_name} ${_board_id} "${lib_sources}" - ARCH ${ARDUINO_CMAKE_PLATFORM_ARCHITECTURE}) + _add_arduino_cmake_library(${_library_name} ${_board_id} "${lib_sources}") endfunction() @@ -46,14 +49,20 @@ function(link_platform_library _target_name _library_name _board_id) message(FATAL_ERROR "Target ${_target_name} doesn't exist - It must be created first!") endif () + parse_scope_argument(scope "${ARGN}" + DEFAULT_SCOPE PUBLIC) + if (NOT TARGET ${_library_name}) + _add_platform_library(${_library_name} ${_board_id}) + get_core_lib_target_name(${_board_id} core_lib_target) _link_arduino_cmake_library(${_target_name} ${_library_name} - PUBLIC + ${scope} BOARD_CORE_TARGET ${core_lib_target}) + else () - target_link_libraries(${_target_name} PUBLIC ${_library_name}) + target_link_libraries(${_target_name} ${scope} ${_library_name}) endif () endfunction() diff --git a/cmake/Platform/Utilities/CMakeArgumentsUtils.cmake b/cmake/Platform/Utilities/CMakeArgumentsUtils.cmake new file mode 100644 index 0000000..47ec858 --- /dev/null +++ b/cmake/Platform/Utilities/CMakeArgumentsUtils.cmake @@ -0,0 +1,114 @@ +#=============================================================================# +# Consumes given arguments for reserved options and single-value options, +# returning a new argument-list without them, no matter where they are positioned. +# It allows easy parsing of arguments which are considered "unlimited", +# limited only by multi-value options. +# _args - Arguments to consume. Usually function's unparsed arguments - ${ARGN}. +# _reserved_options - Reserved option arguments. +# _reserved_single_values - Reserved single-value arguments. +# _return_var - Name of a CMake variable that will hold the extraction result. +# Returns - Original argument-list without reserved options and single-value options. +#=============================================================================# +function(_consume_reserved_arguments _args _reserved_options _reserved_single_values _return_var) + + set(temp_arg_list ${_args}) + + list(LENGTH _args args_length) + decrement_integer(args_length 1) # We'll peform index iteration - It's always length-1 + + foreach (index RANGE ${args_length}) + + list(GET _args ${index} arg) + + if (${arg} IN_LIST _reserved_options) + list(REMOVE_ITEM temp_arg_list ${arg}) + + elseif (${arg} IN_LIST _reserved_single_values) + + # Get the next index to remove as well - It's the option's/key's value + set(next_index ${index}) + increment_integer(next_index 1) + + list(REMOVE_AT temp_arg_list ${index} ${next_index}) + + endif () + + endforeach () + + set(${_return_var} ${temp_arg_list} PARENT_SCOPE) + +endfunction() + +#=============================================================================# +# Parses the given arguments for sources, stopping when all arguments have been read or +# when at least one reserved argument/option has been encountered. +# _reserved_options - Reserved option arguments. +# _reserved_single_values - Reserved single-value arguments. +# _reserved_multi_values - Reserved multi-value arguments. +# _cmake_args - Arguments to parse. Usually function's unparsed arguments - ${ARGN}. +# _return_var - Name of a CMake variable that will hold the extraction result. +# Returns - Parsed sources. +#=============================================================================# +function(parse_sources_arguments _return_var _reserved_options _reserved_single_values + _reserved_multi_values _cmake_args) + + # Initialize argument-lists for further inspection + initialize_list(_reserved_options) + initialize_list(_reserved_single_values) + initialize_list(_reserved_multi_values) + + _consume_reserved_arguments("${_cmake_args}" + "${_reserved_options}" "${_reserved_single_values}" + consumed_args) + + set(sources "") # Clear list because cmake preserves scope in nested functions + + foreach (arg ${consumed_args}) + + if (${arg} IN_LIST _reserved_multi_values) + break() + else () + list(APPEND sources ${arg}) + endif () + + endforeach () + + set(${_return_var} ${sources} PARENT_SCOPE) + +endfunction() + +#=============================================================================# +# Parses the given arguments for scope-controlling arguments, which are PUBLIC|INTERFACE|PRIVATE. +# If none is found, CMake generates an error, unless - The DEFAULT_SCOPE argument is passed. +# _cmake_args - Arguments to parse. Usually function's unparsed arguments - ${ARGN}. +# [DEFAULT_SCOPE] - Optional default scope to return in case none is found in arguments. +# _return_var - Name of a CMake variable that will hold the extraction result. +# Returns - Parsed scope if one is found or the DEFAULT_SCOPE if set, otherwise an error. +#=============================================================================# +function(parse_scope_argument _return_var _cmake_args) + + cmake_parse_arguments(parsed_args "" "DEFAULT_SCOPE" "" ${ARGN}) + + set(scope_options "PRIVATE" "PUBLIC" "INTERFACE") + cmake_parse_arguments(scope_args "${scope_options}" "" "" ${_cmake_args}) + + # Now, link library to executable + if (scope_args_PRIVATE) + set(scope PRIVATE) + elseif (scope_args_INTERFACE) + set(scope INTERFACE) + elseif (scope_args_PUBLIC) + set(scope PUBLIC) + else () + + if (parsed_args_DEFAULT_SCOPE) # Use default scope if none are given + set(scope ${parsed_args_DEFAULT_SCOPE}) + else () + message(SEND_ERROR "Can't parse scope arguments - None are given, or invalid!") + endif () + + endif () + + set(${_return_var} ${scope} PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Utilities/LibraryUtils.cmake b/cmake/Platform/Utilities/LibraryUtils.cmake new file mode 100644 index 0000000..4b6b9c6 --- /dev/null +++ b/cmake/Platform/Utilities/LibraryUtils.cmake @@ -0,0 +1,29 @@ +#=============================================================================# +# Gets the full path to a library's properties file based on the given library root directory. +# If the root ditectory doesn't exist, CMake generates an error and stops. +# If the properties file doesn't exist under the given root directory, +# CMake generates a warning and returns an empty string. +# _library_root_directory - Path to library's root directory. Can be relative. +# _return_var - Name of a CMake variable that will hold the extraction result. +# Returns - Full path to library's properties file if found, otherwise nothing. +#=============================================================================# +function(get_library_properties_file _library_root_directory _return_var) + + get_filename_component(absolute_lib_root_dir ${_library_root_directory} ABSOLUTE) + + if (NOT EXISTS ${absolute_lib_root_dir}) + message(SEND_ERROR "Can't get library's properties file - Root directory doesn't exist.\n" + "Root directory: ${absolute_lib_root_dir}") + endif () + + set(lib_props_file ${absolute_lib_root_dir}/${ARDUINO_CMAKE_LIBRARY_PROPERTIES_FILE_NAME}) + + if (NOT EXISTS ${lib_props_file}) + message(WARNING "Library's properties file doesn't exist under the given root directory.\n" + "Root directory: ${absolute_lib_root_dir}") + return() + endif () + + set(${_return_var} ${lib_props_file} PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Utilities/ListUtils.cmake b/cmake/Platform/Utilities/ListUtils.cmake index 75359dc..987cb67 100644 --- a/cmake/Platform/Utilities/ListUtils.cmake +++ b/cmake/Platform/Utilities/ListUtils.cmake @@ -5,6 +5,23 @@ # Must not be negative or greater than 'list_length'-1. #=============================================================================# macro(list_replace _list _index _new_element) + list(REMOVE_AT ${_list} ${_index}) + list(INSERT ${_list} ${_index} "${_new_element}") + +endmacro() + +#=============================================================================# +# Checks whether the given list is empty. If it is - Initializes it with a theoretically +# impossible to reproduce value, so if it's used to check whether an item is in it +# the answer will be false. +# _list - List to initialize. +#=============================================================================# +macro(initialize_list _list) + + if ("${${_list}}" STREQUAL "") + set(${_list} "+-*/") + endif () + endmacro() diff --git a/cmake/Platform/Utilities/PathUtils.cmake b/cmake/Platform/Utilities/PathUtils.cmake new file mode 100644 index 0000000..d3c370f --- /dev/null +++ b/cmake/Platform/Utilities/PathUtils.cmake @@ -0,0 +1,52 @@ +#=============================================================================# +# Finds the shallowest path among the given sources, where shallowest is the path having +# the least nesting level, i.e. The least number of '/' separators in its' path. +# _sources - List of sources paths to find shallowest path from. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - Shallowest path among given sources (Lowest nesting level). +#=============================================================================# +function(get_shallowest_directory_structure_path _sources _return_var) + + set(min_nesting_level 9999) + + foreach (source ${_sources}) + + string(REGEX MATCHALL "/" nesting_regex_match ${source}) + + list(LENGTH nesting_regex_match source_nesting_level) + + if (${source_nesting_level} LESS ${min_nesting_level}) + set(min_nested_path ${source}) + set(min_nesting_level ${source_nesting_level}) + endif () + + endforeach () + + set(${_return_var} ${min_nested_path} PARENT_SCOPE) + +endfunction() + +#=============================================================================# +# Gets the path of the common root directory of all given sources. +# It is expected that indeed all sources will have the same, common root directory. +# E.g. src/foo.h and src/utility/bar.h both have 'src' in common. +# However, if src/foo.c is a relative path under the C:\ drive (in Windows), and src/bar.c is +# a relative path under the D:\ drive - This is invalid and the function will misbehave. +# _sources - List of sources that have a common root directory which needs to be found. +# _return_var - Name of variable in parent-scope holding the return value. +# Returns - Path to the common root directory of the given list of sources. +#=============================================================================# +function(get_sources_root_directory _sources _return_var) + + get_shallowest_directory_structure_path("${_sources}" shallowest_path) + + get_filename_component(root_dir ${shallowest_path} DIRECTORY) + + if ("${root_dir}" MATCHES ".+src$") # 'src' directory has been retrieved as shallowest path + # The actual root directory is one level above 'src' + get_filename_component(root_dir ${root_dir} DIRECTORY) + endif () + + set(${_return_var} ${root_dir} PARENT_SCOPE) + +endfunction() diff --git a/cmake/Platform/Utilities/StringUtils.cmake b/cmake/Platform/Utilities/StringUtils.cmake index 08a9de2..3efa362 100644 --- a/cmake/Platform/Utilities/StringUtils.cmake +++ b/cmake/Platform/Utilities/StringUtils.cmake @@ -9,6 +9,7 @@ function(get_cmake_compliant_language_name _language _return_var) string(TOLOWER "${_language}" language) + if ("${language}" STREQUAL "s" OR "${language}" STREQUAL "asm") set(language ASM) elseif ("${language}" STREQUAL "cpp" OR "${language}" STREQUAL "cxx" OR @@ -35,6 +36,7 @@ endfunction() function(get_arduino_compliant_language_name _language _return_var) string(TOLOWER "${_language}" language) + if ("${language}" STREQUAL "s" OR "${language}" STREQUAL "asm") set(language S) # Intentionally upper-case elseif ("${language}" STREQUAL "cpp" OR "${language}" STREQUAL "cxx" OR @@ -60,7 +62,9 @@ endfunction() function(get_core_lib_target_name _board_id _return_var) string(REPLACE "." "_" board_id "${_board_id}") + set(core_lib_target_name "${board_id}_core_lib") + string(TOLOWER "${core_lib_target_name}" core_lib_target_name) set(${_return_var} ${core_lib_target_name} PARENT_SCOPE) @@ -76,12 +80,14 @@ endfunction() function(get_name_without_file_extension _input_string _return_var) string(REGEX MATCH "${ARDUINO_CMAKE_NAME_WE_REGEX_PATTERN}" match "${_input_string}") + set(${_return_var} ${CMAKE_MATCH_1} PARENT_SCOPE) endfunction() #=============================================================================# -# Converts a given string a PascalCase string, converting 1st letter to upper and remaining to lower. +# Converts a given string to a PascalCase string, converting 1st letter to upper +# and remaining to lower. # _input_string - String to convert. # _return_var - Name of a CMake variable that will hold the extraction result. # Returns - PascalCase converted string. diff --git a/cmake/Platform/Utilities/Utilities.cmake b/cmake/Platform/Utilities/Utilities.cmake new file mode 100644 index 0000000..92a56c4 --- /dev/null +++ b/cmake/Platform/Utilities/Utilities.cmake @@ -0,0 +1,8 @@ +include(MathUtils) +include(ListUtils) +include(StringUtils) +include(PathUtils) +include(PropertyUtils) +include(LibraryUtils) +include(PlatformLibraryUtils) +include(CMakeArgumentsUtils) diff --git a/examples/3rd-party-library/NeoPixelTest.cpp b/examples/3rd-party-library/NeoPixelTest.cpp index e019c07..ffd7731 100644 --- a/examples/3rd-party-library/NeoPixelTest.cpp +++ b/examples/3rd-party-library/NeoPixelTest.cpp @@ -1,6 +1,3 @@ - -#include - #include "include/NeoPixelTest.hpp" Adafruit_NeoPixel neoPixel;