Browse code

CMake: Add Rust toolchain detection

Micah Snyder authored on 2021/03/05 14:14:08
Showing 3 changed files
... ...
@@ -79,22 +79,11 @@ endif()
79 79
 find_package(PkgConfig QUIET)
80 80
 
81 81
 #
82
-# Find Build Tools
83
-#
84
-
85
-if(MAINTAINER_MODE)
86
-    # Bison, Flex required to build Yara module for libclamav.
87
-    find_package(BISON REQUIRED)
88
-    find_package(FLEX  REQUIRED)
89
-    # TODO: Gperf required to generate JS-normalization code.
90
-    # find_package(GPERF REQUIRED)
91
-endif()
92
-
93
-#
94 82
 # Load Build Options
95 83
 #
96 84
 
97 85
 # CMake Option default values:
86
+set(MAINTAINER_MODE_DEFAULT         OFF)
98 87
 set(ENABLE_APP_DEFAULT              ON)
99 88
 if(WIN32 OR APPLE)
100 89
     set(ENABLE_MILTER_DEFAULT       OFF)
... ...
@@ -120,6 +109,25 @@ set(ENABLE_SYSTEMD_DEFAULT          ON)
120 120
 # See CMakeOptions.cmake for additional options.
121 121
 include(CMakeOptions.cmake)
122 122
 
123
+#
124
+# Find Build Tools
125
+#
126
+if(MAINTAINER_MODE)
127
+    # cbindgen required to update C headers for Rust libraries.
128
+    set(cbindgen_REQUIRED 1)
129
+    set(CBINDGEN ON)
130
+
131
+    # Bison, Flex required to build Yara module for libclamav.
132
+    find_package(BISON REQUIRED)
133
+    find_package(FLEX  REQUIRED)
134
+
135
+    # TODO: Gperf required to generate JS-normalization code.
136
+    # find_package(GPERF REQUIRED)
137
+else()
138
+    set(CBINDGEN OFF)
139
+endif()
140
+find_package(Rust REQUIRED)
141
+
123 142
 if(ENABLE_FUZZ)
124 143
     # We'd like the fuzz targets to be statically linked
125 144
     set(ENABLE_STATIC_LIB       ON)
... ...
@@ -1059,6 +1067,7 @@ ${c}    Compiler:               ${e}
1059 1059
 ${b}        Build type:         ${e}${CMAKE_BUILD_TYPE}
1060 1060
 ${b}        C compiler:         ${e}${CMAKE_C_COMPILER}
1061 1061
 ${b}        C++ compiler:       ${e}${CMAKE_CXX_COMPILER}
1062
+${b}        Rust toolchain:     ${e}${cargo_EXECUTABLE} (${cargo_VERSION})
1062 1063
 ${b}        CFLAGS:             ${e}${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS}
1063 1064
 ${b}        CXXFLAGS:           ${e}${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS}
1064 1065
 ${b}        WARNCFLAGS:         ${e}${WARNCFLAGS}")
... ...
@@ -1079,7 +1088,15 @@ ${b}        Enable UnRAR:       ${e}${ENABLE_UNRAR}
1079 1079
 ${b}        Examples:           ${e}${ENABLE_EXAMPLES}
1080 1080
 ${b}        Tests:              ${e}${ENABLE_TESTS}
1081 1081
 ${b}        Build man pages:    ${e}${ENABLE_MAN_PAGES}
1082
-${b}        Build doxygen HTML: ${e}${ENABLE_DOXYGEN}")
1082
+${b}        Build doxygen HTML: ${e}${ENABLE_DOXYGEN}
1083
+${b}        Maintainer Mode:    ${e}${MAINTAINER_MODE}")
1084
+if(MAINTAINER_MODE)
1085
+message("\
1086
+${R}    Maintainer Mode Tools:  ${e}
1087
+${y}        cbindgen:           ${e}${cbindgen_EXECUTABLE} (${cbindgen_VERSION})
1088
+${y}        bison:              ${e}${BISON_EXECUTABLE} (${BISON_VERSION})
1089
+${y}        flex:               ${e}${FLEX_EXECUTABLE} (${FLEX_VERSION})")
1090
+endif()
1083 1091
 if(NOT WIN32)
1084 1092
 message("\
1085 1093
 ${c}    Build Extras:           ${e}")
... ...
@@ -34,6 +34,10 @@ option(OPTIMIZE
34 34
     "Allow compiler optimizations.  Set to OFF to disable (i.e. to set -O0)."
35 35
     ON)
36 36
 
37
+option(MAINTAINER_MODE
38
+    "Update generated sources. Requires flex, bison, cbindgen."
39
+    ${MAINTAINER_MODE_DEFAULT})
40
+
37 41
 option(ENABLE_WERROR
38 42
     "Compile time warnings will cause build failures.")
39 43
 
40 44
new file mode 100644
... ...
@@ -0,0 +1,255 @@
0
+# Find the Rust toolchain and add the `add_rust_library()` API to build Rust
1
+# libraries.
2
+#
3
+# Copyright (C) 2021 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4
+#
5
+# Author: Micah Snyder
6
+# To see this in a sample project, visit: https://github.com/micahsnyder/cmake-rust-demo
7
+#
8
+# Code to set the Cargo arguments was lifted from:
9
+#   https://github.com/Devolutions/CMakeRust
10
+#
11
+# This Module defines the following variables:
12
+#  - <program>_FOUND      - True if the program was found
13
+#  - <program>_EXECUTABLE - path of the program
14
+#  - <program>_VERSION    - version number of the program
15
+#
16
+# ... for the following Rust toolchain programs:
17
+#  - cargo
18
+#  - rustc
19
+#  - rustup
20
+#  - rust-gdb
21
+#  - rust-lldb
22
+#  - rustdoc
23
+#  - rustfmt
24
+#  - bindgen
25
+#  - cbindgen
26
+#
27
+# Note that `cbindgen` is presently 3rd-party, and is not included with the
28
+# standard Rust installation. `bindgen` is a part of the rust toolchain, but
29
+# might need to be installed separately.
30
+#
31
+# Callers can make any program mandatory by setting `<program>_REQUIRED` before
32
+# the call to `find_package(Rust)`
33
+#
34
+# Eg:
35
+#
36
+#    if(MAINTAINER_MODE)
37
+#        set(cbindgen_REQUIRED 1)
38
+#        set(bindgen_REQUIRED 1)
39
+#    endif()
40
+#    find_package(Rust REQUIRED)
41
+#
42
+# This module also provides an `add_rust_library()` function which allows a
43
+# caller to create a Rust static library target which you can link to with
44
+# `target_link_libraries()`.
45
+#
46
+# Your Rust static library target will itself depend on the native static libs
47
+# you get from `rustc --crate-type staticlib --print=native-static-libs /dev/null`
48
+#
49
+# Example `add_rust_library()` usage:
50
+#
51
+#    add_rust_library(TARGET yourlib WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
52
+#    add_library(YourProject::yourlib ALIAS yourlib)
53
+#
54
+#    add_executable(yourexe)
55
+#    target_link_libraries(yourexe YourProject::yourlib)
56
+#
57
+
58
+if(NOT DEFINED CARGO_HOME)
59
+    if(WIN32)
60
+        set(CARGO_HOME "$ENV{USERPROFILE}/.cargo")
61
+    else()
62
+        set(CARGO_HOME "$ENV{HOME}/.cargo")
63
+    endif()
64
+endif()
65
+
66
+include(FindPackageHandleStandardArgs)
67
+
68
+function(find_rust_program RUST_PROGRAM)
69
+    find_program(${RUST_PROGRAM}_EXECUTABLE ${RUST_PROGRAM}
70
+        HINTS "${CARGO_HOME}"
71
+        PATH_SUFFIXES "bin"
72
+    )
73
+
74
+    if(${RUST_PROGRAM}_EXECUTABLE)
75
+        execute_process(COMMAND "${${RUST_PROGRAM}_EXECUTABLE}" --version
76
+            OUTPUT_VARIABLE ${RUST_PROGRAM}_VERSION_OUTPUT
77
+            ERROR_VARIABLE  ${RUST_PROGRAM}_VERSION_ERROR
78
+            RESULT_VARIABLE ${RUST_PROGRAM}_VERSION_RESULT
79
+        )
80
+        if(NOT ${${RUST_PROGRAM}_VERSION_RESULT} EQUAL 0)
81
+            message(STATUS "Rust tool `${RUST_PROGRAM}` not found: Failed to determine version.")
82
+            unset(${RUST_PROGRAM}_EXECUTABLE)
83
+        else()
84
+            string(REGEX
85
+                MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?(-nightly)?"
86
+                ${RUST_PROGRAM}_VERSION "${${RUST_PROGRAM}_VERSION_OUTPUT}"
87
+            )
88
+            set(${RUST_PROGRAM}_VERSION "${${RUST_PROGRAM}_VERSION}" PARENT_SCOPE)
89
+            message(STATUS "Rust tool `${RUST_PROGRAM}` found: ${${RUST_PROGRAM}_EXECUTABLE}, ${${RUST_PROGRAM}_VERSION}")
90
+        endif()
91
+
92
+        mark_as_advanced(${RUST_PROGRAM}_EXECUTABLE ${RUST_PROGRAM}_VERSION)
93
+    else()
94
+        if(${${RUST_PROGRAM}_REQUIRED})
95
+            message(FATAL_ERROR "Rust tool `${RUST_PROGRAM}` not found.")
96
+        else()
97
+            message(STATUS "Rust tool `${RUST_PROGRAM}` not found.")
98
+        endif()
99
+    endif()
100
+endfunction()
101
+
102
+function(add_rust_library)
103
+    set(options)
104
+    set(oneValueArgs TARGET WORKING_DIRECTORY)
105
+    cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
106
+
107
+    if(WIN32)
108
+        set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/${ARGS_TARGET}.lib")
109
+    else()
110
+        set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}/lib${ARGS_TARGET}.a")
111
+    endif()
112
+
113
+    file(GLOB_RECURSE LIB_SOURCES "${ARGS_WORKING_DIRECTORY}/*.rs")
114
+
115
+    set(MY_CARGO_ARGS ${CARGO_ARGS})
116
+    list(APPEND MY_CARGO_ARGS "--target-dir" ${CMAKE_CURRENT_BINARY_DIR})
117
+    list(JOIN MY_CARGO_ARGS " " MY_CARGO_ARGS_STRING)
118
+
119
+    # Build the library and generate the c-binding, if `cbindgen` is required.
120
+    if(${cbindgen_REQUIRED})
121
+        add_custom_command(
122
+            OUTPUT "${OUTPUT}"
123
+            COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} ARGS ${MY_CARGO_ARGS}
124
+            COMMAND ${cbindgen_EXECUTABLE} --lang c -o ${ARGS_WORKING_DIRECTORY}/${ARGS_TARGET}.h ${ARGS_WORKING_DIRECTORY}
125
+            WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}"
126
+            DEPENDS ${LIB_SOURCES}
127
+            COMMENT "Building ${ARGS_TARGET} in ${ARGS_WORKING_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}")
128
+    else()
129
+        add_custom_command(
130
+            OUTPUT "${OUTPUT}"
131
+            COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} ARGS ${MY_CARGO_ARGS}
132
+            WORKING_DIRECTORY "${ARGS_WORKING_DIRECTORY}"
133
+            DEPENDS ${LIB_SOURCES}
134
+            COMMENT "Building ${ARGS_TARGET} in ${ARGS_WORKING_DIRECTORY} with:\n\t ${cargo_EXECUTABLE} ${MY_CARGO_ARGS_STRING}")
135
+    endif()
136
+
137
+    # Create a target from the build output
138
+    add_custom_target(${ARGS_TARGET}_target
139
+        DEPENDS ${OUTPUT})
140
+
141
+    # Create a static imported library target from library target
142
+    add_library(${ARGS_TARGET} STATIC IMPORTED GLOBAL)
143
+    add_dependencies(${ARGS_TARGET} ${ARGS_TARGET}_target)
144
+    target_link_libraries(${ARGS_TARGET} INTERFACE ${RUST_NATIVE_STATIC_LIBS})
145
+
146
+    # Specify where the library is and where to find the headers
147
+    set_target_properties(${ARGS_TARGET}
148
+        PROPERTIES
149
+            IMPORTED_LOCATION "${OUTPUT}"
150
+            INTERFACE_INCLUDE_DIRECTORIES "${ARGS_WORKING_DIRECTORY}"
151
+    )
152
+endfunction()
153
+
154
+function(add_rust_test)
155
+    set(options)
156
+    set(oneValueArgs NAME WORKING_DIRECTORY)
157
+    cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
158
+
159
+    add_test(
160
+        NAME test-${ARGS_NAME}
161
+        COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${cargo_EXECUTABLE} test -vv --color always
162
+        WORKING_DIRECTORY ${ARGS_WORKING_DIRECTORY}
163
+    )
164
+endfunction()
165
+
166
+#
167
+# Cargo is the primary tool for using the Rust Toolchain to to build static
168
+# libs that can include other crate dependencies.
169
+#
170
+find_rust_program(cargo)
171
+
172
+# These other programs may also be useful...
173
+find_rust_program(rustc)
174
+find_rust_program(rustup)
175
+find_rust_program(rust-gdb)
176
+find_rust_program(rust-lldb)
177
+find_rust_program(rustdoc)
178
+find_rust_program(rustfmt)
179
+find_rust_program(bindgen)
180
+find_rust_program(cbindgen)
181
+
182
+# Determine the native libs required to link w/ rust static libs
183
+# message(STATUS "Detecting native static libs for rust: ${rustc_EXECUTABLE} --crate-type staticlib --print=native-static-libs /dev/null")
184
+execute_process(
185
+    COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${rustc_EXECUTABLE} --crate-type staticlib --print=native-static-libs /dev/null
186
+    OUTPUT_VARIABLE RUST_NATIVE_STATIC_LIBS_OUTPUT
187
+    ERROR_VARIABLE  RUST_NATIVE_STATIC_LIBS_ERROR
188
+    RESULT_VARIABLE RUST_NATIVE_STATIC_LIBS_RESULT
189
+)
190
+string(REGEX REPLACE "\r?\n" ";" LINE_LIST "${RUST_NATIVE_STATIC_LIBS_ERROR}")
191
+foreach(LINE ${LINE_LIST})
192
+    # do the match on each line
193
+    string(REGEX MATCH "native-static-libs: .*" LINE "${LINE}")
194
+    if(NOT LINE)
195
+        continue()
196
+    endif()
197
+    string(REPLACE "native-static-libs: " "" LINE "${LINE}")
198
+    string(REGEX REPLACE "  " "" LINE "${LINE}")
199
+    string(REGEX REPLACE " " ";" LINE "${LINE}")
200
+    if(LINE)
201
+        message(STATUS "Rust's native static libs: ${LINE}")
202
+        set(RUST_NATIVE_STATIC_LIBS "${LINE}")
203
+        break()
204
+    endif()
205
+endforeach()
206
+
207
+if(WIN32)
208
+    if(CMAKE_SIZEOF_VOID_P EQUAL 8)
209
+        set(LIB_TARGET "x86_64-pc-windows-msvc")
210
+    else()
211
+        set(LIB_TARGET "i686-pc-windows-msvc")
212
+    endif()
213
+elseif(ANDROID)
214
+    if(ANDROID_SYSROOT_ABI STREQUAL "x86")
215
+        set(LIB_TARGET "i686-linux-android")
216
+    elseif(ANDROID_SYSROOT_ABI STREQUAL "x86_64")
217
+        set(LIB_TARGET "x86_64-linux-android")
218
+    elseif(ANDROID_SYSROOT_ABI STREQUAL "arm")
219
+        set(LIB_TARGET "arm-linux-androideabi")
220
+    elseif(ANDROID_SYSROOT_ABI STREQUAL "arm64")
221
+        set(LIB_TARGET "aarch64-linux-android")
222
+    endif()
223
+elseif(IOS)
224
+    set(LIB_TARGET "universal")
225
+elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin)
226
+    set(LIB_TARGET "x86_64-apple-darwin")
227
+else()
228
+    if(CMAKE_SIZEOF_VOID_P EQUAL 8)
229
+        set(LIB_TARGET "x86_64-unknown-linux-gnu")
230
+    else()
231
+        set(LIB_TARGET "i686-unknown-linux-gnu")
232
+    endif()
233
+endif()
234
+
235
+if(IOS)
236
+    set(CARGO_ARGS "lipo")
237
+else()
238
+    set(CARGO_ARGS "build")
239
+    list(APPEND CARGO_ARGS "--target" ${LIB_TARGET})
240
+endif()
241
+
242
+if(NOT CMAKE_BUILD_TYPE)
243
+    set(LIB_BUILD_TYPE "debug")
244
+elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release")
245
+    set(LIB_BUILD_TYPE "release")
246
+    list(APPEND CARGO_ARGS "--release")
247
+else()
248
+    set(LIB_BUILD_TYPE "debug")
249
+endif()
250
+
251
+find_package_handle_standard_args( Rust
252
+    REQUIRED_VARS cargo_EXECUTABLE
253
+    VERSION_VAR cargo_VERSION
254
+)