Skip to content

Commit 603460c

Browse files
committed
Preprocess sketches the Arduino way
Fixes: #9
1 parent a5596d7 commit 603460c

File tree

5 files changed

+353
-4
lines changed

5 files changed

+353
-4
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ enable_testing()
55
add_custom_target(
66
toolchain SOURCES
77
toolchain/Arduino/RulesOverride.cmake
8+
toolchain/Arduino/ScriptMode.cmake
89
toolchain/Platform/Arduino.cmake
10+
toolchain/Scripts/Preprocess.cmake
911
toolchain/Templates/ArduinoLibraryCMakeLists.txt.in
12+
toolchain/Templates/PreprocessConfig.cmake.in
1013
toolchain/arduino-cli-toolchain.cmake)
1114

1215
add_custom_target(

toolchain/Arduino/ScriptMode.cmake

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
cmake_policy(SET CMP0057 NEW)
3+
4+
if (NOT CMAKE_SCRIPT_MODE_FILE)
5+
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE} must be run in script mode.")
6+
return()
7+
endif()
8+
9+
# ----------------------------------------------------------------------------------------------------------------------
10+
# Verifies that all required variables are defined for running the current CMake script
11+
# ----------------------------------------------------------------------------------------------------------------------
12+
function(arduino_script_require) # [ARGUMENT_NAME...]
13+
message(TRACE "Validating parameters for ${CMAKE_SCRIPT_MODE_FILE}")
14+
15+
cmake_parse_arguments(REQUIRE "" "" "OPTIONAL" ${ARGV})
16+
17+
foreach(_parameter IN LISTS REQUIRE_UNPARSED_ARGUMENTS REQUIRE_OPTIONAL)
18+
if (NOT DEFINED "${_parameter}")
19+
message(FATAL_ERROR "The required parameter ${_parameter} is missing.")
20+
elseif (NOT ${_parameter} AND NOT "${_parameter}" IN_LIST REQUIRE_OPTIONAL)
21+
message(FATAL_ERROR "The required parameter ${_parameter} is empty")
22+
endif()
23+
24+
message(TRACE " ${_parameter}: ${${_parameter}}")
25+
endforeach()
26+
endfunction()
27+
28+
# ----------------------------------------------------------------------------------------------------------------------
29+
# Some generally useful variables
30+
# ----------------------------------------------------------------------------------------------------------------------
31+
string(ASCII 35 _hash)

toolchain/Scripts/Preprocess.cmake

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
include(Arduino/ScriptMode NO_POLICY_SCOPE)
2+
include("${ARGUMENTS}" OPTIONAL)
3+
4+
arduino_script_require(
5+
ARDUINO_CTAGS_EXECUTABLE # the full path to the ctags executable used by arduino-cli
6+
PREPROCESS_MODE # the preprocessing mode: "SKETCH" or "SOURCE"
7+
PREPROCESS_SOURCE_DIRPATH # base directory for for source files
8+
PREPROCESS_SOURCE_FILENAME # the path of the source file to preprocess
9+
PREPROCESS_OUTPUT_DIRPATH # base directory for preprocessed sketches
10+
PREPROCESS_OUTPUT_FILEPATH # where to store the preprocessed code
11+
12+
OPTIONAL
13+
PREPROCESS_OTHER_SKETCHES # additional sketches which get merged with `PREPROCESS_SOURCE_FILENAME`
14+
)
15+
16+
# ----------------------------------------------------------------------------------------------------------------------
17+
# Runs ctags on `FILEPATH...` and reports the findings from ctags in `OUTPUT_VARIABLE`
18+
# ----------------------------------------------------------------------------------------------------------------------
19+
function(__arduino_ctags OUTPUT_VARIABLE FILEPATH) # [FILEPATH...]
20+
# FIXME figure out why android-cli also collects structs and variables (--c++-kinds=svpf)
21+
22+
execute_process(
23+
COMMAND "${ARDUINO_CTAGS_EXECUTABLE}"
24+
--language-force=C++ --c++-kinds=pf --fields=KSTtzns
25+
--sort=no -nf - ${ARGN} "${OUTPUT_FILEPATH}"
26+
OUTPUT_VARIABLE _ctags_output RESULT_VARIABLE _result)
27+
28+
if (NOT _result EQUAL 0)
29+
message(FATAL_ERROR "Running ctags has failed on ${OUTPUT_FILEPATH}:\n${_ctags_output}")
30+
return()
31+
endif()
32+
33+
string(REPLACE ";" ":" _ctags_output "${_ctags_output}")
34+
string(REPLACE "\n" ";" _ctags_output "${_ctags_output}")
35+
36+
set("${OUTPUT_VARIABLE}" ${_ctags_output} PARENT_SCOPE)
37+
endfunction()
38+
39+
# ----------------------------------------------------------------------------------------------------------------------
40+
# Runs ctags on `FILEPATH...` and reports the findings from ctags in `OUTPUT_VARIABLE`
41+
# ----------------------------------------------------------------------------------------------------------------------
42+
function(__arduino_preprocess_sketches SOURCE_DIRPATH MAIN_FILENAME OTHER_FILENAME_LIST OUTPUT_DIRPATH OUTPUT_FILEPATH)
43+
message(VERBOSE "Preprocessing ${MAIN_FILENAME} as Arduino sketch")
44+
45+
unset(_combined_text) # <----------------------------------------- combine all the sketches into one single C++ file
46+
47+
foreach(_filename IN ITEMS "${MAIN_FILENAME}" LISTS OTHER_FILENAME_LIST)
48+
cmake_path(
49+
ABSOLUTE_PATH _filename
50+
BASE_DIRECTORY "${SOURCE_DIRPATH}"
51+
OUTPUT_VARIABLE _filepath)
52+
53+
message(VERBOSE " Merging ${_filepath}")
54+
file(READ "${_filepath}" _text)
55+
56+
string(APPEND _combined_text "${_hash}line 1 \"${_filepath}\"\n${_text}")
57+
endforeach()
58+
59+
file(WRITE "${OUTPUT_FILEPATH}" "${_combined_text}")
60+
61+
__arduino_ctags(_symbols "${OUTPUT_FILEPATH}" --line-directives=no) # <-------- find first declaration in merged C++
62+
63+
list(GET _symbols 0 _first_symbol)
64+
65+
if (NOT _first_symbol MATCHES "\tline:([0-9]+).*")
66+
message(FATAL_ERROR "Could not find any symbols in ctags output")
67+
return()
68+
else()
69+
math(EXPR _line_before_first_symbol "${CMAKE_MATCH_1} - 2")
70+
endif()
71+
72+
__arduino_ctags(_symbols "${OUTPUT_FILEPATH}" --line-directives=yes) # <------- extract declarations from merged C++
73+
74+
unset(_prototypes)
75+
unset(_first_symbol_tags)
76+
77+
foreach(_line IN LISTS _symbols)
78+
string(REGEX REPLACE ".*\tkind:([^\t]+).*" "\\1" _type "${_line}")
79+
string(REGEX REPLACE "^([^\t]+)\t.*" "\\1" _name "${_line}")
80+
string(REGEX REPLACE ".*\tsignature:([^\t]+).*" "\\1" _args "${_line}")
81+
string(REGEX REPLACE ".*\treturntype:([^\t]+).*" "\\1" _return "${_line}")
82+
string(REGEX REPLACE "^[^\t]+\t([^\t]+)\t.*" "\\1" _filepath "${_line}")
83+
string(REGEX REPLACE ".*\tline:([^\t]+).*" "\\1" _line "${_line}")
84+
85+
if (NOT _filepath)
86+
continue()
87+
endif()
88+
89+
string(REPLACE "${OUTPUT_DIRPATH}/" "" _filepath "${_filepath}") # <---------- repair paths for ctags on Windows
90+
string(APPEND _prototypes "${_hash}line ${_line} \"${_filepath}\"\n")
91+
92+
if (NOT _first_symbol_tags)
93+
set(_first_symbol_tags "${_prototypes}")
94+
endif()
95+
96+
string(APPEND _prototypes "${_return} ${_name}${_args};\n")
97+
endforeach()
98+
99+
string(APPEND _prototypes "${_first_symbol_tags}") # <----------------------- preserve position of first declaration
100+
101+
unset(_text_before_first_symbol) # <------------------------------- backup all the code before the first declaration
102+
103+
if (_line_before_first_symbol)
104+
foreach(_index RANGE ${_line_before_first_symbol})
105+
string(APPEND _text_before_first_symbol "[^\n]*\n")
106+
endforeach()
107+
endif()
108+
109+
if (_text_before_first_symbol)
110+
string(REGEX MATCH "^${_text_before_first_symbol}" _match "${_combined_text}")
111+
string(LENGTH "${_match}" _length)
112+
else()
113+
set(_length 0)
114+
endif()
115+
116+
string(SUBSTRING "${_combined_text}" 0 ${_length} _before_prototypes) # <------- split code around first declaration
117+
string(SUBSTRING "${_combined_text}" ${_length} -1 _after_prototypes)
118+
119+
file(WRITE "${OUTPUT_FILEPATH}" # <------------------------------------ rebuild source code with injected prototypes
120+
"${_hash}include <Arduino.h>\n"
121+
"${_before_prototypes}"
122+
"${_prototypes}"
123+
"${_after_prototypes}")
124+
endfunction()
125+
126+
# ----------------------------------------------------------------------------------------------------------------------
127+
# Simply copies SOURCE_FILEPATH to OUTPUT_FILEPATH and prepends a preprocessor directive
128+
# to track the origin of this file for user-friendly compiler errors.
129+
# ----------------------------------------------------------------------------------------------------------------------
130+
function(__arduino_preprocess_regular_sources SOURCE_DIRPATH SOURCE_FILENAME OUTPUT_FILEPATH)
131+
message(VERBOSE "Preprocessing ${SOURCE_FILENAME} as regular source code")
132+
133+
cmake_path(
134+
ABSOLUTE_PATH SOURCE_FILENAME
135+
BASE_DIRECTORY "${SOURCE_DIRPATH}"
136+
OUTPUT_VARIABLE _filepath)
137+
138+
message(STATUS "_filepath: ${_filepath}")
139+
140+
file(READ "${_filepath}" _text)
141+
142+
file(WRITE "${OUTPUT_FILEPATH}"
143+
"${_hash}line 1 \"${_filepath}\"\n"
144+
"${_text}")
145+
endfunction()
146+
147+
# ----------------------------------------------------------------------------------------------------------------------
148+
# The main routine of this module.
149+
# ----------------------------------------------------------------------------------------------------------------------
150+
if (PREPROCESS_MODE STREQUAL "SKETCH")
151+
__arduino_preprocess_sketches(
152+
"${PREPROCESS_SOURCE_DIRPATH}" "${PREPROCESS_SOURCE_FILENAME}" "${PREPROCESS_OTHER_SKETCHES}"
153+
"${PREPROCESS_OUTPUT_DIRPATH}" "${PREPROCESS_OUTPUT_FILEPATH}")
154+
elseif (PREPROCESS_MODE STREQUAL "SOURCE")
155+
__arduino_preprocess_regular_sources(
156+
"${PREPROCESS_SOURCE_DIRPATH}" "${PREPROCESS_SOURCE_FILENAME}"
157+
"${PREPROCESS_OUTPUT_FILEPATH}")
158+
else()
159+
message(FATAL_ERROR "Invalid mode ${PREPROCESS_MODE}")
160+
endif()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set(ARDUINO_CTAGS_EXECUTABLE "${ARDUINO_CTAGS_EXECUTABLE}")
2+
set(PREPROCESS_MODE "${MODE}")
3+
set(PREPROCESS_SOURCE_DIRPATH "${SOURCE_DIRPATH}")
4+
set(PREPROCESS_SOURCE_FILENAME "${SOURCE_FILENAME}")
5+
set(PREPROCESS_OTHER_SKETCHES "${OTHER_SKETCHES}")
6+
set(PREPROCESS_OUTPUT_DIRPATH "${OUTPUT_DIRPATH}")
7+
set(PREPROCESS_OUTPUT_FILEPATH "${_output_filepath}")

0 commit comments

Comments
 (0)