Skip to content

Commit d5968af

Browse files
committed
Scan preprocessed code for implicit dependencies
Fixes: #5
1 parent f06ca95 commit d5968af

File tree

5 files changed

+249
-3
lines changed

5 files changed

+249
-3
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ add_custom_target(
77
toolchain/Arduino/RulesOverride.cmake
88
toolchain/Arduino/ScriptMode.cmake
99
toolchain/Platform/Arduino.cmake
10+
toolchain/Scripts/CollectLibraries.cmake
1011
toolchain/Scripts/Preprocess.cmake
1112
toolchain/Templates/ArduinoLibraryCMakeLists.txt.in
13+
toolchain/Templates/CollectLibrariesConfig.cmake.in
1214
toolchain/Templates/PreprocessConfig.cmake.in
1315
toolchain/arduino-cli-toolchain.cmake)
1416

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
include(Arduino/ScriptMode NO_POLICY_SCOPE)
2+
include("${ARGUMENTS}" OPTIONAL)
3+
4+
arduino_script_require(
5+
COLLECT_LIBRARIES_CACHE
6+
COLLECT_LIBRARIES_TARGET
7+
COLLECT_LIBRARIES_OUTPUT
8+
COLLECT_LIBRARIES_SOURCES)
9+
10+
# ----------------------------------------------------------------------------------------------------------------------
11+
# Extracts include directives from the files in `SOURCES_LIST` and stores the filename list in `OUTPUT_VARIABLE`.
12+
# ----------------------------------------------------------------------------------------------------------------------
13+
function(__arduino_collect_required_libraries SOURCES_LIST OUTPUT_VARIABLE)
14+
unset(_required_includes)
15+
16+
foreach(_filepath IN LISTS SOURCES_LIST)
17+
message(STATUS " Scanning ${_filepath} for include directives")
18+
19+
# FIXME actually run preprocessor on the file to handle conditional includes (#ifdef/#else)
20+
file(STRINGS "${_filepath}" _include_directives REGEX "^${_hash}[ /t]*include")
21+
22+
foreach(_line IN LISTS _include_directives)
23+
if (_line MATCHES "${_hash}[ /t]*include[ /t]*<([^>]+\\.[Hh])>")
24+
list(APPEND _required_includes "${CMAKE_MATCH_1}")
25+
endif()
26+
endforeach()
27+
endforeach()
28+
29+
list(REMOVE_DUPLICATES _required_includes)
30+
list(REMOVE_ITEM _required_includes "Arduino.h")
31+
32+
set("${OUTPUT_VARIABLE}" ${_required_includes} PARENT_SCOPE)
33+
endfunction()
34+
35+
# ----------------------------------------------------------------------------------------------------------------------
36+
# Filters the known installed libraries by `LOCATION` and reports the result in `OUTPUT_VARIABLE`.
37+
# The result is a list of `(type, name, dirpath)` tuples separated by '|'.
38+
# ----------------------------------------------------------------------------------------------------------------------
39+
function(__arduino_find_installed_libraries LOCATION OUTPUT_VARIABLE)
40+
message(VERBOSE "Reading installed libraries from ${COLLECT_LIBRARIES_CACHE}")
41+
file(READ "${COLLECT_LIBRARIES_CACHE}" _installed_libraries)
42+
43+
string(JSON _installed_libraries GET "${_installed_libraries}" "installed_libraries")
44+
string(JSON _count LENGTH "${_installed_libraries}")
45+
math(EXPR _last "${_count} - 1")
46+
47+
unset(_library_list)
48+
49+
foreach(_library_index RANGE ${_last})
50+
string(JSON _dirpath GET "${_installed_libraries}" ${_library_index} "library" "source_dir")
51+
string(JSON _type GET "${_installed_libraries}" ${_library_index} "library" "location")
52+
string(JSON _name GET "${_installed_libraries}" ${_library_index} "library" "name")
53+
54+
cmake_path(NORMAL_PATH _dirpath)
55+
list(APPEND _library_list "${_type}|${_name}|${_dirpath}")
56+
endforeach()
57+
58+
set("${OUTPUT_VARIABLE}" ${_library_list} PARENT_SCOPE)
59+
endfunction()
60+
61+
# ----------------------------------------------------------------------------------------------------------------------
62+
# Splits a `LIBRARY` tuple into its components `(type, name, dirpath)`.
63+
# ----------------------------------------------------------------------------------------------------------------------
64+
function(__arduino_split_library_tuple LIBRARY TYPE_VARIABLE NAME_VARIABLE DIRPATH_VARIABLE)
65+
string(REGEX MATCH "^([^|]+)\\|([^|]+)\\|([^|]+)" _ "${_library}")
66+
67+
set("${TYPE_VARIABLE}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
68+
set("${NAME_VARIABLE}" "${CMAKE_MATCH_2}" PARENT_SCOPE)
69+
set("${DIRPATH_VARIABLE}" "${CMAKE_MATCH_3}" PARENT_SCOPE)
70+
endfunction()
71+
72+
# ----------------------------------------------------------------------------------------------------------------------
73+
# Tries to find the libraries that provide `REQUIRED_INCLUDES`
74+
# and stores the identified library tuples in `OUTPUT_VARIABLE`.
75+
# ----------------------------------------------------------------------------------------------------------------------
76+
function(__arduino_resolve_libraries REQUIRED_INCLUDES OUTPUT_VARIABLE)
77+
# FIXME Read LINK_LIBRARIES of COLLECT_LIBRARIES_TARGET
78+
79+
# Cluster the libraries reported by arduino-cli by location to implement the location priority
80+
# described by https://arduino.github.io/arduino-cli/1.0/sketch-build-process/#location-priority
81+
82+
__arduino_find_installed_libraries("user" _user_librarys)
83+
__arduino_find_installed_libraries("platform" _platform_librarys)
84+
__arduino_find_installed_libraries("ref-platform" _board_librarys)
85+
__arduino_find_installed_libraries("ide" _ide_libraries)
86+
87+
unset(_resolved_includes)
88+
unset(_required_libraries)
89+
unset(_unresolved_includes)
90+
91+
while (REQUIRED_INCLUDES)
92+
list(POP_FRONT REQUIRED_INCLUDES _next_include)
93+
message(VERBOSE " Searching library that provides ${_next_include}")
94+
95+
unset(_matching_library)
96+
97+
foreach(_library IN LISTS _user_librarys _platform_librarys _board_librarys _ide_libraries)
98+
__arduino_split_library_tuple("${_library}" _type _name _dirpath)
99+
message(TRACE "Checking ${_type} library ${_name} at ${_dirpath}")
100+
101+
if (EXISTS "${_dirpath}/${_next_include}"
102+
AND NOT IS_DIRECTORY "${_dirpath}/${_next_include}")
103+
set(_matching_library "${_library}")
104+
break()
105+
endif()
106+
endforeach()
107+
108+
if (_matching_library)
109+
message(VERBOSE " Using ${_next_include} from ${_type} library ${_name} at ${_dirpath}")
110+
111+
list(APPEND _resolved_includes "${_next_include}")
112+
list(APPEND _required_libraries "${_matching_library}")
113+
else()
114+
list(APPEND _unresolved_includes "${_next_include}")
115+
endif()
116+
endwhile()
117+
118+
if (_unresolved_includes)
119+
list(JOIN _unresolved_includes ", " _unresolved_includes)
120+
message(WARNING "Could not resolve all required libraries. Unresolved includes: ${_unresolved_includes}")
121+
endif()
122+
123+
list(REMOVE_DUPLICATES _required_libraries)
124+
set("${OUTPUT_VARIABLE}" "${_required_libraries}" PARENT_SCOPE)
125+
endfunction()
126+
127+
# ----------------------------------------------------------------------------------------------------------------------
128+
# Generates a CMake file that defines import libraries from `REQUIRED_LIBRARIES` and links `TARGET` with them.
129+
# ----------------------------------------------------------------------------------------------------------------------
130+
function(__arduino_generate_library_definitions TARGET REQUIRED_LIBRARIES OUTPUT_FILEPATH)
131+
set(_library_definitions "# Generated by ${CMAKE_SCRIPT_MODE_FILE}")
132+
unset(_link_libraries)
133+
134+
foreach(_library IN LISTS REQUIRED_LIBRARIES) # <--------------------- generate __arduino_add_import_library() calls
135+
__arduino_split_library_tuple("${_library}" _ _name _dirpath)
136+
string(REPLACE " " "_" _target_name "${_name}")
137+
138+
if (_target_name MATCHES "[^A-Za-z0-9]_")
139+
message(FATAL_ERROR "Unexpected character '${CMAKE_MATCH_0}' in library name '${_name}'")
140+
return()
141+
endif()
142+
143+
list(
144+
APPEND _library_definitions
145+
""
146+
"if (NOT TARGET Arduino::${_target_name})"
147+
" __arduino_add_import_library(${_target_name} \"${_dirpath}\")"
148+
"endif()")
149+
150+
list(APPEND _link_libraries "Arduino::${_target_name}")
151+
endforeach()
152+
153+
if (_link_libraries) # <-------------------------------------------- link `TARGET` with the defined import libraries
154+
list(APPEND _library_definitions
155+
""
156+
"target_link_libraries(\"${TARGET}\" PUBLIC ${_link_libraries})")
157+
endif()
158+
159+
list(JOIN _library_definitions "\n" _library_definitions) # <------------ write the definitions to `OUTPUT_FILEPATH`
160+
161+
if (EXISTS "${OUTPUT_FILEPATH}")
162+
file(READ "${OUTPUT_FILEPATH}" _previous_definitions)
163+
else()
164+
unset(_previous_definitions)
165+
endif()
166+
167+
if (NOT _library_definitions STREQUAL _previous_definitions)
168+
message(STATUS "Generating ${OUTPUT_FILEPATH}")
169+
file(WRITE "${OUTPUT_FILEPATH}" "${_library_definitions}")
170+
endif()
171+
endfunction()
172+
173+
__arduino_collect_required_libraries("${COLLECT_LIBRARIES_SOURCES}" _required_includes)
174+
__arduino_resolve_libraries("${_required_includes}" _required_libraries)
175+
176+
__arduino_generate_library_definitions(
177+
"${COLLECT_LIBRARIES_TARGET}" "${_required_libraries}"
178+
"${COLLECT_LIBRARIES_OUTPUT}")

toolchain/Templates/ArduinoLibraryCMakeLists.txt.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ add_library(
1010
target_include_directories(
1111
${_libname} PUBLIC
1212
"${_quoted_library_directories}")
13+
14+
if (NOT "${_libname}" STREQUAL "ArduinoCore")
15+
target_link_libraries(${_libname} PUBLIC Arduino::Core)
16+
endif()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
set(COLLECT_LIBRARIES_CACHE "${__ARDUINO_INSTALLED_LIBRARIES_CACHE}")
2+
set(COLLECT_LIBRARIES_OUTPUT "${_required_libraries_include}")
3+
set(COLLECT_LIBRARIES_SOURCES "${_preprocessed_sources_list}")
4+
set(COLLECT_LIBRARIES_TARGET "${TARGET}")

toolchain/arduino-cli-toolchain.cmake

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ function(__arduino_preprocess OUTPUT_VARIABLE OUTPUT_DIRPATH SOURCE_DIRPATH MODE
957957
"${OUTPUT_DIRPATH}" _output_filepath)
958958

959959
string(MD5 _filepath_hash "${_output_filepath}")
960-
set(_config_filepath "${CMAKE_BINARY_DIR}/ArduinoFiles/${_target}/preprocess-config-${_filepath_hash}.cmake")
960+
set(_config_filepath "${CMAKE_BINARY_DIR}/ArduinoFiles/${_target}/preprocess-${_filepath_hash}.cmake")
961961

962962
__arduino_add_code_generator(
963963
SCRIPT_OUTPUT "${_output_filepath}"
@@ -996,6 +996,61 @@ function(__arduino_preprocess_sketch TARGET OUTPUT_DIRPATH SOURCE_DIRPATH SOURCE
996996
endforeach()
997997
endfunction()
998998

999+
# ----------------------------------------------------------------------------------------------------------------------
1000+
# Remove obsolete files from preprocesses the sources files directory.
1001+
# Such step is neccessary as the prebuild hooks of various cores place files in this folder to implement dynamic
1002+
# dependency chains. Therefore this folder has to be searched for files to resolve dependencies, instead of using
1003+
# a computed list. For this search to succeed previous build-artifacts must be removed.
1004+
# ----------------------------------------------------------------------------------------------------------------------
1005+
function(__arduino_remove_obsolete_sketch_files TARGET SKETCH_DIRPATH)
1006+
set(_source_list_cache "${CMAKE_BINARY_DIR}/ArduinoFiles/${TARGET}/sources.txt")
1007+
1008+
if (EXISTS "${_source_list_cache}") # <----------------------------------- read target SOURCE list from previous run
1009+
file(READ "${_source_list_cache}" _cached_source_list)
1010+
else()
1011+
unset(_cached_source_list)
1012+
endif()
1013+
1014+
get_property(_source_list TARGET "${TARGET}" PROPERTY SOURCES) # <---------- check if files got removed from SOURCES
1015+
1016+
list(REMOVE_DUPLICATES _source_list)
1017+
list(REMOVE_ITEM _cached_source_list ${_source_list})
1018+
file(WRITE "${_source_list_cache}" "${_source_list}")
1019+
1020+
foreach(_filename IN LISTS _cached_source_list) # <------------------- delete preprocessed files for removed SOURCES
1021+
__arduino_resolve_preprocessed_filepath(
1022+
"${_source_dirpath}" "${_filename}"
1023+
"${SKETCH_DIRPATH}" _sketch_filepath)
1024+
1025+
message(STATUS "Removing obsolete ${_filename}")
1026+
file(REMOVE "${_sketch_filepath}")
1027+
endforeach()
1028+
endfunction()
1029+
1030+
# ----------------------------------------------------------------------------------------------------------------------
1031+
# Scan the preprocessed source code of `TARGET` for #include directives to detect implicit library dependencies.
1032+
# ----------------------------------------------------------------------------------------------------------------------
1033+
function(__arduino_link_implicit_libraries TARGET SKETCH_DIRPATH)
1034+
# Can't simple use a pre-computed source file list here, but have to list files in SKETCH_DIRPATH
1035+
# since the pre-build hooks of some platforms place generated files in the sketch folder to enable
1036+
# selective linking of system libraries.
1037+
__arduino_remove_obsolete_sketch_files("${_target}" "${SKETCH_DIRPATH}")
1038+
__arduino_collect_source_files(_preprocessed_sources_list "${SKETCH_DIRPATH}")
1039+
1040+
set(_required_libraries_include "${CMAKE_BINARY_DIR}/ArduinoFiles/${_target}/libraries-include.cmake")
1041+
target_sources("${_target}" PRIVATE "${_required_libraries_include}")
1042+
1043+
__arduino_add_code_generator(
1044+
SCRIPT_OUTPUT "${_required_libraries_include}"
1045+
SCRIPT_FILEPATH "${__ARDUINO_TOOLCHAIN_COLLECT_LIBRARIES}"
1046+
CONFIG_TEMPLATE "${ARDUINO_TOOLCHAIN_DIR}/Templates/CollectLibrariesConfig.cmake.in"
1047+
CONFIG_FILEPATH "${CMAKE_BINARY_DIR}/ArduinoFiles/${_target}/libraries-config.cmake"
1048+
COMMENT "Collecting required libraries for ${TARGET}"
1049+
DEPENDS ${_preprocessed_sources_list})
1050+
1051+
include("${_required_libraries_include}")
1052+
endfunction()
1053+
9991054
# ----------------------------------------------------------------------------------------------------------------------
10001055
# Iterates all subdirectories of the project and finalizes Arduino sketches.
10011056
# ----------------------------------------------------------------------------------------------------------------------
@@ -1043,7 +1098,8 @@ function(__arduino_toolchain_finalize DIRECTORY)
10431098
"${_target}" "${_sketch_dirpath}"
10441099
"${_source_dirpath}" "${_source_list}")
10451100

1046-
target_link_libraries("${_target}" PUBLIC Arduino::Core)
1101+
target_link_libraries("${_target}" PUBLIC Arduino::Core) # <------------- link implicitly required libraries
1102+
__arduino_link_implicit_libraries("${_target}" "${_sketch_dirpath}")
10471103

10481104
set_property(TARGET "${_target}" PROPERTY SUFFIX ".elf") # <----------------------- build the final firmware
10491105
__arduino_add_firmware_target("${_target}" _firmware_filename)
@@ -1100,9 +1156,11 @@ cmake_path(GET CMAKE_CURRENT_LIST_FILE PARENT_PATH ARDUINO_TOOLCHAIN_DIR) # <---
11001156
list(APPEND CMAKE_MODULE_PATH ${ARDUINO_TOOLCHAIN_DIR})
11011157

11021158
set(__ARDUINO_SKETCH_SUFFIX "\\.(ino|pde)\$") # <-------------------------------------------- generally useful constants
1103-
set(__ARDUINO_TOOLCHAIN_PREPROCESS "${ARDUINO_TOOLCHAIN_DIR}/Scripts/Preprocess.cmake")
1159+
set(__ARDUINO_TOOLCHAIN_COLLECT_LIBRARIES "${ARDUINO_TOOLCHAIN_DIR}/Scripts/CollectLibraries.cmake")
1160+
set(__ARDUINO_TOOLCHAIN_PREPROCESS "${ARDUINO_TOOLCHAIN_DIR}/Scripts/Preprocess.cmake")
11041161

11051162
list(APPEND CMAKE_CONFIGURE_DEPENDS # <------------------------------------- rerun CMake when helper scripts are changed
1163+
"${__ARDUINO_TOOLCHAIN_COLLECT_LIBRARIES}"
11061164
"${__ARDUINO_TOOLCHAIN_PREPROCESS}")
11071165

11081166
find_program( # <-------------------------------------------------------------------------------------- find android-cli

0 commit comments

Comments
 (0)