Refactor our integration of LFSC (#6201)
[cvc5.git] / cmake / targetLinkLibrariesWithDynamicLookup.cmake
1 #
2 # - This module provides the function
3 # target_link_libraries_with_dynamic_lookup which can be used to
4 # "weakly" link a loadable module.
5 #
6 # Link a library to a target such that the symbols are resolved at
7 # run-time not link-time. This should be used when compiling a
8 # loadable module when the symbols should be resolve from the run-time
9 # environment where the module is loaded, and not a specific system
10 # library.
11 #
12 # Specifically, for OSX it uses undefined dynamic_lookup. This is
13 # similar to using "-shared" on Linux where undefined symbols are
14 # ignored.
15 #
16 # Additionally, the linker is checked to see if it supports undefined
17 # symbols when linking a shared library. If it does then the library
18 # is not linked when specified with this function.
19 #
20 # http://blog.tim-smith.us/2015/09/python-extension-modules-os-x/
21 #
22 #
23 # The following functions are defined:
24 #
25 # _get_target_type(<ResultVar> <Target>)
26 #
27 # **INTERNAL** Shorthand for querying an abbreviated version of the target type
28 # of the given ``<Target>``. ``<ResultVar>`` is set to "STATIC" for a
29 # STATIC_LIBRARY, "SHARED" for a SHARED_LIBRARY, "MODULE" for a MODULE_LIBRARY,
30 # and "EXE" for an EXECUTABLE.
31 #
32 # Defined variables:
33 #
34 # ``<ResultVar>``
35 # The abbreviated version of the ``<Target>``'s type.
36 #
37 #
38 # _test_weak_link_project(<TargetType>
39 # <LibType>
40 # <ResultVar>
41 # <LinkFlagsVar>)
42 #
43 # **INTERNAL** Attempt to compile and run a test project where a target of type
44 # ``<TargetType>`` is weakly-linked against a dependency of type ``<LibType>``.
45 # ``<TargetType>`` can be one of "STATIC", "SHARED", "MODULE", or "EXE".
46 # ``<LibType>`` can be one of "STATIC", "SHARED", or "MODULE".
47 #
48 # Defined variables:
49 #
50 # ``<ResultVar>``
51 # Whether the current C toolchain can produce a working target binary of type
52 # ``<TargetType>`` that is weakly-linked against a dependency target of type
53 # ``<LibType>``.
54 #
55 # ``<LinkFlagsVar>``
56 # List of flags to add to the linker command to produce a working target
57 # binary of type ``<TargetType>`` that is weakly-linked against a dependency
58 # target of type ``<LibType>``.
59 #
60 #
61 # check_dynamic_lookup(<TargetType>
62 # <LibType>
63 # <ResultVar>
64 # <LinkFlagsVar>)
65 #
66 # Check if the linker requires a command line flag to allow leaving symbols
67 # unresolved when producing a target of type ``<TargetType>`` that is
68 # weakly-linked against a dependency of type ``<LibType>``. ``<TargetType>``
69 # can be one of "STATIC", "SHARED", "MODULE", or "EXE". ``<LibType>`` can be
70 # one of "STATIC", "SHARED", or "MODULE". The result is cached between
71 # invocations and recomputed only when the value of CMake's linker flag list
72 # changes; ``CMAKE_STATIC_LINKER_FLAGS`` if ``<TargetType>`` is "STATIC", and
73 # ``CMAKE_SHARED_LINKER_FLAGS`` otherwise.
74 #
75 #
76 # Defined variables:
77 #
78 # ``<ResultVar>``
79 # Whether the current C toolchain supports weak-linking for target binaries of
80 # type ``<TargetType>`` that are weakly-linked against a dependency target of
81 # type ``<LibType>``.
82 #
83 # ``<LinkFlagsVar>``
84 # List of flags to add to the linker command to produce a working target
85 # binary of type ``<TargetType>`` that is weakly-linked against a dependency
86 # target of type ``<LibType>``.
87 #
88 # ``HAS_DYNAMIC_LOOKUP_<TargetType>_<LibType>``
89 # Cached, global alias for ``<ResultVar>``
90 #
91 # ``DYNAMIC_LOOKUP_FLAGS_<TargetType>_<LibType>``
92 # Cached, global alias for ``<LinkFlagsVar>``
93 #
94 #
95 # target_link_libraries_with_dynamic_lookup(<Target> [<Libraries>])
96 #
97 # Like proper linking, except that the given ``<Libraries>`` are not necessarily
98 # linked. Instead, the ``<Target>`` is produced in a manner that allows for
99 # symbols unresolved within it to be resolved at runtime, presumably by the
100 # given ``<Libraries>``. If such a target can be produced, the provided
101 # ``<Libraries>`` are not actually linked. On platforms that do not support
102 # weak-linking, this function works just like ``target_link_libraries``.
103
104 function(_get_target_type result_var target)
105 set(target_type "SHARED_LIBRARY")
106 if(TARGET ${target})
107 get_property(target_type TARGET ${target} PROPERTY TYPE)
108 endif()
109
110 set(result "STATIC")
111
112 if(target_type STREQUAL "STATIC_LIBRARY")
113 set(result "STATIC")
114 endif()
115
116 if(target_type STREQUAL "SHARED_LIBRARY")
117 set(result "SHARED")
118 endif()
119
120 if(target_type STREQUAL "MODULE_LIBRARY")
121 set(result "MODULE")
122 endif()
123
124 if(target_type STREQUAL "EXECUTABLE")
125 set(result "EXE")
126 endif()
127
128 set(${result_var} ${result} PARENT_SCOPE)
129 endfunction()
130
131
132 function(_test_weak_link_project
133 target_type
134 lib_type
135 can_weak_link_var
136 project_name)
137
138 set(gnu_ld_ignore "-Wl,--unresolved-symbols=ignore-all")
139 set(osx_dynamic_lookup "-undefined dynamic_lookup")
140 set(no_flag "")
141
142 foreach(link_flag_spec gnu_ld_ignore osx_dynamic_lookup no_flag)
143 set(link_flag "${${link_flag_spec}}")
144
145 set(test_project_dir "${PROJECT_BINARY_DIR}/CMakeTmp")
146 set(test_project_dir "${test_project_dir}/${project_name}")
147 set(test_project_dir "${test_project_dir}/${link_flag_spec}")
148 set(test_project_dir "${test_project_dir}/${target_type}")
149 set(test_project_dir "${test_project_dir}/${lib_type}")
150
151 set(test_project_src_dir "${test_project_dir}/src")
152 set(test_project_bin_dir "${test_project_dir}/build")
153
154 file(MAKE_DIRECTORY ${test_project_src_dir})
155 file(MAKE_DIRECTORY ${test_project_bin_dir})
156
157 set(mod_type "STATIC")
158 set(link_mod_lib TRUE)
159 set(link_exe_lib TRUE)
160 set(link_exe_mod FALSE)
161
162 if("${target_type}" STREQUAL "EXE")
163 set(link_exe_lib FALSE)
164 set(link_exe_mod TRUE)
165 else()
166 set(mod_type "${target_type}")
167 endif()
168
169 if("${mod_type}" STREQUAL "MODULE")
170 set(link_mod_lib FALSE)
171 endif()
172
173
174 file(WRITE "${test_project_src_dir}/CMakeLists.txt" "
175 cmake_minimum_required(VERSION ${CMAKE_VERSION})
176 project(${project_name} C)
177
178 include_directories(${test_project_src_dir})
179
180 add_library(number ${lib_type} number.c)
181 add_library(counter ${mod_type} counter.c)
182 ")
183
184 if("${mod_type}" STREQUAL "MODULE")
185 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
186 set_target_properties(counter PROPERTIES PREFIX \"\")
187 ")
188 endif()
189
190 if(link_mod_lib)
191 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
192 target_link_libraries(counter number)
193 ")
194 elseif(NOT link_flag STREQUAL "")
195 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
196 set_target_properties(counter PROPERTIES LINK_FLAGS \"${link_flag}\")
197 ")
198 endif()
199
200 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
201 add_executable(main main.c)
202 ")
203
204 if(link_exe_lib)
205 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
206 target_link_libraries(main number)
207 ")
208 elseif(NOT link_flag STREQUAL "")
209 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
210 target_link_libraries(main \"${link_flag}\")
211 ")
212 endif()
213
214 if(link_exe_mod)
215 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
216 target_link_libraries(main counter)
217 ")
218 else()
219 file(APPEND "${test_project_src_dir}/CMakeLists.txt" "
220 target_link_libraries(main \"${CMAKE_DL_LIBS}\")
221 ")
222 endif()
223
224 file(WRITE "${test_project_src_dir}/number.c" "
225 #include <number.h>
226
227 static int _number;
228 void set_number(int number) { _number = number; }
229 int get_number() { return _number; }
230 ")
231
232 file(WRITE "${test_project_src_dir}/number.h" "
233 #ifndef _NUMBER_H
234 #define _NUMBER_H
235 extern void set_number(int);
236 extern int get_number(void);
237 #endif
238 ")
239
240 file(WRITE "${test_project_src_dir}/counter.c" "
241 #include <number.h>
242 int count() {
243 int result = get_number();
244 set_number(result + 1);
245 return result;
246 }
247 ")
248
249 file(WRITE "${test_project_src_dir}/counter.h" "
250 #ifndef _COUNTER_H
251 #define _COUNTER_H
252 extern int count(void);
253 #endif
254 ")
255
256 file(WRITE "${test_project_src_dir}/main.c" "
257 #include <stdlib.h>
258 #include <stdio.h>
259 #include <number.h>
260 ")
261
262 if(NOT link_exe_mod)
263 file(APPEND "${test_project_src_dir}/main.c" "
264 #include <dlfcn.h>
265 ")
266 endif()
267
268 file(APPEND "${test_project_src_dir}/main.c" "
269 int my_count() {
270 int result = get_number();
271 set_number(result + 1);
272 return result;
273 }
274
275 int main(int argc, char **argv) {
276 int result;
277 ")
278
279 if(NOT link_exe_mod)
280 file(APPEND "${test_project_src_dir}/main.c" "
281 void *counter_module;
282 int (*count)(void);
283
284 counter_module = dlopen(\"./counter.so\", RTLD_LAZY | RTLD_GLOBAL);
285 if(!counter_module) goto error;
286
287 count = dlsym(counter_module, \"count\");
288 if(!count) goto error;
289 ")
290 endif()
291
292 file(APPEND "${test_project_src_dir}/main.c" "
293 result = count() != 0 ? EXIT_FAILURE :
294 my_count() != 1 ? EXIT_FAILURE :
295 my_count() != 2 ? EXIT_FAILURE :
296 count() != 3 ? EXIT_FAILURE :
297 count() != 4 ? EXIT_FAILURE :
298 count() != 5 ? EXIT_FAILURE :
299 my_count() != 6 ? EXIT_FAILURE : EXIT_SUCCESS;
300 ")
301
302 if(NOT link_exe_mod)
303 file(APPEND "${test_project_src_dir}/main.c" "
304 goto done;
305 error:
306 fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror());
307 result = 1;
308
309 done:
310 if(counter_module) dlclose(counter_module);
311 ")
312 endif()
313
314 file(APPEND "${test_project_src_dir}/main.c" "
315 return result;
316 }
317 ")
318
319 set(_rpath_arg)
320 if(APPLE AND ${CMAKE_VERSION} VERSION_GREATER 2.8.11)
321 set(_rpath_arg "-DCMAKE_MACOSX_RPATH='${CMAKE_MACOSX_RPATH}'")
322 endif()
323
324 try_compile(project_compiles
325 "${test_project_bin_dir}"
326 "${test_project_src_dir}"
327 "${project_name}"
328 CMAKE_FLAGS
329 "-DCMAKE_SHARED_LINKER_FLAGS='${CMAKE_SHARED_LINKER_FLAGS}'"
330 ${_rpath_arg}
331 OUTPUT_VARIABLE compile_output)
332
333 set(project_works 1)
334 set(run_output)
335
336 if(project_compiles)
337 execute_process(COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR}
338 "${test_project_bin_dir}/main"
339 WORKING_DIRECTORY "${test_project_bin_dir}"
340 RESULT_VARIABLE project_works
341 OUTPUT_VARIABLE run_output
342 ERROR_VARIABLE run_output)
343 endif()
344
345 set(test_description
346 "Weak Link ${target_type} -> ${lib_type} (${link_flag_spec})")
347
348 if(project_works EQUAL 0)
349 set(project_works TRUE)
350 message(STATUS "Performing Test ${test_description} - Success")
351 else()
352 set(project_works FALSE)
353 message(STATUS "Performing Test ${test_description} - Failed")
354 file(APPEND ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CMakeError.log
355 "Performing Test ${test_description} failed with the "
356 "following output:\n"
357 "BUILD\n-----\n${compile_output}\nRUN\n---\n${run_output}\n")
358 endif()
359
360 set(${can_weak_link_var} ${project_works} PARENT_SCOPE)
361 if(project_works)
362 set(${project_name} ${link_flag} PARENT_SCOPE)
363 break()
364 endif()
365 endforeach()
366 endfunction()
367
368
369 function(check_dynamic_lookup
370 target_type
371 lib_type
372 has_dynamic_lookup_var
373 link_flags_var)
374
375 # hash the CMAKE_FLAGS passed and check cache to know if we need to rerun
376 if("${target_type}" STREQUAL "STATIC")
377 string(MD5 cmake_flags_hash "${CMAKE_STATIC_LINKER_FLAGS}")
378 else()
379 string(MD5 cmake_flags_hash "${CMAKE_SHARED_LINKER_FLAGS}")
380 endif()
381
382 set(cache_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}")
383 set(cache_hash_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}_hash")
384 set(result_var "DYNAMIC_LOOKUP_FLAGS_${target_type}_${lib_type}")
385
386 if( NOT DEFINED ${cache_hash_var}
387 OR NOT "${${cache_hash_var}}" STREQUAL "${cmake_flags_hash}")
388 unset(${cache_var} CACHE)
389 endif()
390
391 if(NOT DEFINED ${cache_var})
392 set(skip_test FALSE)
393
394 if(NOT CMAKE_CROSSCOMPILING)
395 set(skip_test TRUE)
396 elseif(CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR)
397 set(skip_test TRUE)
398 endif()
399
400 if(skip_test)
401 set(has_dynamic_lookup FALSE)
402 set(link_flags)
403 else()
404 _test_weak_link_project(${target_type}
405 ${lib_type}
406 has_dynamic_lookup
407 link_flags)
408 endif()
409
410 set(caveat " (when linking ${target_type} against ${lib_type})")
411
412 set(${cache_var} "${has_dynamic_lookup}"
413 CACHE BOOL
414 "linker supports dynamic lookup for undefined symbols${caveat}")
415
416 set(${result_var} "${link_flags}"
417 CACHE BOOL
418 "linker flags for dynamic lookup${caveat}")
419
420 set(${cache_hash_var} "${cmake_flags_hash}"
421 CACHE INTERNAL "hashed flags for ${cache_var} check")
422 endif()
423
424 set(${has_dynamic_lookup_var} "${${cache_var}}" PARENT_SCOPE)
425 set(${link_flags_var} "${${result_var}}" PARENT_SCOPE)
426 endfunction()
427
428
429 function(target_link_libraries_with_dynamic_lookup target)
430 _get_target_type(target_type ${target})
431
432 set(link_props)
433 set(link_items)
434 set(link_libs)
435
436 foreach(lib ${ARGN})
437 _get_target_type(lib_type ${lib})
438 check_dynamic_lookup(${target_type}
439 ${lib_type}
440 has_dynamic_lookup
441 dynamic_lookup_flags)
442
443 if(has_dynamic_lookup)
444 if(dynamic_lookup_flags)
445 if("${target_type}" STREQUAL "EXE")
446 list(APPEND link_items "${dynamic_lookup_flags}")
447 else()
448 list(APPEND link_props "${dynamic_lookup_flags}")
449 endif()
450 endif()
451 else()
452 list(APPEND link_libs "${lib}")
453 endif()
454 endforeach()
455
456 if(link_props)
457 list(REMOVE_DUPLICATES link_props)
458 endif()
459
460 if(link_items)
461 list(REMOVE_DUPLICATES link_items)
462 endif()
463
464 if(link_libs)
465 list(REMOVE_DUPLICATES link_libs)
466 endif()
467
468 if(link_props)
469 set_target_properties(${target}
470 PROPERTIES LINK_FLAGS "${link_props}")
471 endif()
472
473 set(links "${link_items}" "${link_libs}")
474 if(links)
475 target_link_libraries(${target} "${links}")
476 endif()
477 endfunction()
478