Skip to content

Commit 56e490f

Browse files
authored
Merge pull request #14 from hasselmm/9-preprocess-and-merge-sketches
Preprocess sketches the Arduino way
2 parents 7e3208d + 7ad7929 commit 56e490f

File tree

8 files changed

+413
-48
lines changed

8 files changed

+413
-48
lines changed

CMakeLists.txt

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

1215
add_custom_target(
1316
tools SOURCES

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ How about using `CMakeLists.txt` files as simple as this?
66

77
```CMake
88
cmake_minimum_required(VERSION 3.19)
9-
project(CMakeBlink VERSION 0.1 LANGUAGES CXX)
9+
project(CMakeBlink LANGUAGES CXX)
1010
add_executable(CMakeBlink CMakeBlink.ino)
1111
```
1212

tests/CMakeBlink/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
cmake_minimum_required(VERSION 3.19)
2-
project(CMakeBlink VERSION 0.1 LANGUAGES CXX)
2+
project(CMakeBlink LANGUAGES CXX)
33
add_executable(CMakeBlink CMakeBlink.ino)

toolchain/Arduino/RulesOverride.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
set(CMAKE_ASM_OUTPUT_EXTENSION ".o") # <---------------------- align object file extensions with Arduino for convenience
1+
set(CMAKE_ASM_OUTPUT_EXTENSION ".o")
22
set(CMAKE_C_OUTPUT_EXTENSION ".o")
33
set(CMAKE_CXX_OUTPUT_EXTENSION ".o")
44

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: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
set(ENV{TMP} "${OUTPUT_DIRPATH}/ctags/first-run") # work around Arduino's ctags having problems with tempfiles
62+
file(MAKE_DIRECTORY "$ENV{TMP}")
63+
64+
__arduino_ctags(_symbols "${OUTPUT_FILEPATH}" --line-directives=no) # <-------- find first declaration in merged C++
65+
66+
list(GET _symbols 0 _first_symbol)
67+
68+
if (NOT _first_symbol MATCHES "\tline:([0-9]+).*")
69+
message(FATAL_ERROR "Could not find any symbols in ctags output")
70+
return()
71+
else()
72+
math(EXPR _line_before_first_symbol "${CMAKE_MATCH_1} - 2")
73+
endif()
74+
75+
set(ENV{TMP} "${OUTPUT_DIRPATH}/ctags/second-run") # work around Arduino's ctags having problems with tempfiles
76+
file(MAKE_DIRECTORY "$ENV{TMP}")
77+
78+
__arduino_ctags(_symbols "${OUTPUT_FILEPATH}" --line-directives=yes) # <------- extract declarations from merged C++
79+
80+
unset(_prototypes)
81+
unset(_first_symbol_tags)
82+
83+
foreach(_line IN LISTS _symbols)
84+
string(REGEX REPLACE ".*\tkind:([^\t]+).*" "\\1" _type "${_line}")
85+
string(REGEX REPLACE "^([^\t]+)\t.*" "\\1" _name "${_line}")
86+
string(REGEX REPLACE ".*\tsignature:([^\t]+).*" "\\1" _args "${_line}")
87+
string(REGEX REPLACE ".*\treturntype:([^\t]+).*" "\\1" _return "${_line}")
88+
string(REGEX REPLACE "^[^\t]+\t([^\t]+)\t.*" "\\1" _filepath "${_line}")
89+
string(REGEX REPLACE ".*\tline:([^\t]+).*" "\\1" _line "${_line}")
90+
91+
if (NOT _filepath)
92+
continue()
93+
endif()
94+
95+
string(REPLACE "${OUTPUT_DIRPATH}/" "" _filepath "${_filepath}") # <---------- repair paths for ctags on Windows
96+
string(APPEND _prototypes "${_hash}line ${_line} \"${_filepath}\"\n")
97+
98+
if (NOT _first_symbol_tags)
99+
set(_first_symbol_tags "${_prototypes}")
100+
endif()
101+
102+
string(APPEND _prototypes "${_return} ${_name}${_args};\n")
103+
endforeach()
104+
105+
string(APPEND _prototypes "${_first_symbol_tags}") # <----------------------- preserve position of first declaration
106+
107+
unset(_text_before_first_symbol) # <------------------------------- backup all the code before the first declaration
108+
109+
if (_line_before_first_symbol)
110+
foreach(_index RANGE ${_line_before_first_symbol})
111+
string(APPEND _text_before_first_symbol "[^\n]*\n")
112+
endforeach()
113+
endif()
114+
115+
if (_text_before_first_symbol)
116+
string(REGEX MATCH "^${_text_before_first_symbol}" _match "${_combined_text}")
117+
string(LENGTH "${_match}" _length)
118+
else()
119+
set(_length 0)
120+
endif()
121+
122+
string(SUBSTRING "${_combined_text}" 0 ${_length} _before_prototypes) # <------- split code around first declaration
123+
string(SUBSTRING "${_combined_text}" ${_length} -1 _after_prototypes)
124+
125+
file(WRITE "${OUTPUT_FILEPATH}" # <------------------------------------ rebuild source code with injected prototypes
126+
"${_hash}include <Arduino.h>\n"
127+
"${_before_prototypes}"
128+
"${_prototypes}"
129+
"${_after_prototypes}")
130+
endfunction()
131+
132+
# ----------------------------------------------------------------------------------------------------------------------
133+
# Simply copies SOURCE_FILEPATH to OUTPUT_FILEPATH and prepends a preprocessor directive
134+
# to track the origin of this file for user-friendly compiler errors.
135+
# ----------------------------------------------------------------------------------------------------------------------
136+
function(__arduino_preprocess_regular_sources SOURCE_DIRPATH SOURCE_FILENAME OUTPUT_FILEPATH)
137+
message(VERBOSE "Preprocessing ${SOURCE_FILENAME} as regular source code")
138+
139+
cmake_path(
140+
ABSOLUTE_PATH SOURCE_FILENAME
141+
BASE_DIRECTORY "${SOURCE_DIRPATH}"
142+
OUTPUT_VARIABLE _filepath)
143+
144+
message(STATUS "_filepath: ${_filepath}")
145+
146+
file(READ "${_filepath}" _text)
147+
148+
file(WRITE "${OUTPUT_FILEPATH}"
149+
"${_hash}line 1 \"${_filepath}\"\n"
150+
"${_text}")
151+
endfunction()
152+
153+
# ----------------------------------------------------------------------------------------------------------------------
154+
# The main routine of this module.
155+
# ----------------------------------------------------------------------------------------------------------------------
156+
if (PREPROCESS_MODE STREQUAL "SKETCH")
157+
__arduino_preprocess_sketches(
158+
"${PREPROCESS_SOURCE_DIRPATH}" "${PREPROCESS_SOURCE_FILENAME}" "${PREPROCESS_OTHER_SKETCHES}"
159+
"${PREPROCESS_OUTPUT_DIRPATH}" "${PREPROCESS_OUTPUT_FILEPATH}")
160+
elseif (PREPROCESS_MODE STREQUAL "SOURCE")
161+
__arduino_preprocess_regular_sources(
162+
"${PREPROCESS_SOURCE_DIRPATH}" "${PREPROCESS_SOURCE_FILENAME}"
163+
"${PREPROCESS_OUTPUT_FILEPATH}")
164+
else()
165+
message(FATAL_ERROR "Invalid mode ${PREPROCESS_MODE}")
166+
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)