-
Notifications
You must be signed in to change notification settings - Fork 31
/
IgnUtils.cmake
1786 lines (1452 loc) · 68.9 KB
/
IgnUtils.cmake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#################################################
# ign_find_package(<PACKAGE_NAME>
# [REQUIRED] [PRIVATE] [EXACT] [QUIET] [BUILD_ONLY] [PKGCONFIG_IGNORE]
# [COMPONENTS <components_of_PACKAGE_NAME>]
# [OPTIONAL_COMPONENTS <components_of_PACKAGE_NAME>]
# [REQUIRED_BY <components_of_project>]
# [PRIVATE_FOR <components_of_project>]
# [VERSION <ver>]
# [EXTRA_ARGS <args>]
# [PRETTY <name>]
# [PURPOSE <"explanation for this dependency">]
# [PKGCONFIG <pkgconfig_name>]
# [PKGCONFIG_LIB <lib_name>]
# [PKGCONFIG_VER_COMPARISON < > = <= >= ])
#
# This is a wrapper for the standard cmake find_package which behaves according
# to the conventions of the ignition library. In particular, we do not quit
# immediately when a required package is missing. Instead, we check all
# dependencies and provide an overview of what is missing at the end of the
# configuration process. Descriptions of the function arguments are as follows:
#
# <PACKAGE_NAME>: The name of the package as it would normally be passed to
# find_package(~). Note if your package corresponds to a
# find-module named FindABC.cmake, then <PACKAGE_NAME> must be
# ABC, with the case matching. If the find-module is named
# FindAbc.cmake, then <PACKAGE_NAME> must be Abc. This will not
# necessarily match the library's actual name, nor will it
# necessarily match the name used by pkgconfig, so there are
# additional arguments (i.e. PRETTY, PKGCONFIG) to specify
# alternative names for this package that can be used depending
# on the context.
#
# [REQUIRED]: Optional. If provided, macro will trigger an ignition build_error
# when the package cannot be found. If not provided, this macro will
# trigger an ignition build_warning when the package is not found.
# To specify that something is required by some set of components
# (rather than the core library), use REQUIRED_BY.
#
# [PRIVATE]: Optional. Use this to indicate that consumers of the project do not
# need to link against the package, but it must be present on the
# system, because our project must link against it.
#
# [EXACT]: Optional. This will pass on the EXACT option to find_package(~) and
# also add it to the call to find_dependency(~) in the
# <project>-config.cmake file.
#
# [QUIET]: Optional. If provided, it will be passed forward to cmake's
# find_package(~) command. This macro will still print its normal
# output, except there will be no warning if the package is missing,
# unless REQUIRED or REQUIRED_BY is specified.
#
# [BUILD_ONLY]: Optional. Use this to indicate that the project only needs this
# package while building, and it does not need to be available to
# the consumer of this project at all. Normally this should only
# apply to (1) a header-only library whose headers are included
# exclusively in the source files and not included in any public
# (i.e. installed) project headers, or to (2) a static library
# dependency.
#
# [PKGCONFIG_IGNORE]: Discouraged. If this option is provided, this package will
# not be added to the project's pkgconfig file in any way.
# This should only be used in very rare circumstances. Note
# that BUILD_ONLY will also prevent a pkgconfig entry from
# being produced.
#
# [COMPONENTS]: Optional. If provided, the list that follows it will be passed
# to find_package(~) to indicate which components of PACKAGE_NAME
# are considered to be dependencies of either this project
# (specified by REQUIRED) or this project's components (specified
# by REQUIRED_BY). This is effectively the same as the
# find_package( ... COMPONENTS <components>) argument.
#
# [REQUIRED_BY]: Optional. If provided, the list that follows it must indicate
# which library components require the dependency. Note that if
# REQUIRED is specified, then REQUIRED_BY does NOT need to be
# specified for any components which depend on the core library,
# because their dependence on this package will effectively be
# inherited from the core library. This will trigger a build
# warning to tell the user which component requires this
# dependency.
#
# [PRIVATE_FOR]: Optional. If provided, the list that follows it must indicate
# which library components depend on this package privately (i.e.
# the package should not be included in its list of interface
# libraries). This is only relevant for components that follow
# the REQUIRED_BY command. Note that the PRIVATE argument does
# not apply to components specified by REQUIRED_BY. This argument
# MUST be given for components whose private dependencies have
# been specified with REQUIRED_BY.
#
# [VERSION]: Optional. Follow this argument with the major[.minor[.patch[.tweak]]]
# version that you need for this package.
#
# [EXTRA_ARGS]: Optional. Additional args to pass forward to find_package(~)
#
# [PRETTY]: Optional. If provided, the string that follows will replace
# <PACKAGE_NAME> when printing messages, warnings, or errors to the
# terminal.
#
# [PURPOSE]: Optional. If provided, the string that follows will be appended to
# the build_warning or build_error that this function produces when
# the package could not be found.
#
# ==========================================================================
# The following arguments pertain to the automatic generation of your
# project's pkgconfig file. Ideally, this information should be provided
# automatically by ignition-cmake through the cmake find-module that is written
# for your dependency. However, if your package gets distributed with its own
# cmake config-file or find-module, then it might not automatically set this
# information. Therefore, we provide the ability to set it through your call to
# ign_find_package(~). These arguments can also be used to overwrite the
# pkg-config entries that get generated by the ign-cmake find-module for the
# package. Do not hesitate to ask for help if you need to use these arguments.
#
# [PKGCONFIG]: Optional. If provided, the string that follows will be used to
# specify a "required package" for pkgconfig. Note that the option
# PKGCONFIG_LIB has higher precedence than this option.
#
# [PKGCONFIG_LIB]: Optional. Use this to indicate that the package should be
# considered a "library" by pkgconfig. This is used for
# libraries which do not come with *.pc metadata, such as
# system libraries, libm, libdl, or librt. Generally you should
# leave this out, because most packages will be considered
# "modules" by pkgconfig. The string which follows this
# argument will be used as the library name, and the string
# that follows a PKGCONFIG argument will be ignored, so the
# PKGCONFIG argument can be left out when using this argument.
#
# [PKGCONFIG_VER_COMPARISON]: Optional. If provided, pkgconfig will be told how
# the available version of this package must compare
# to the specified version. Acceptable values are
# =, <, >, <=, >=. Default will be =. If no version
# is provided using VERSION, then this will be left
# out, whether or not it is provided.
#
macro(ign_find_package PACKAGE_NAME)
#------------------------------------
# Define the expected arguments
set(options REQUIRED PRIVATE EXACT QUIET BUILD_ONLY PKGCONFIG_IGNORE)
set(oneValueArgs VERSION PRETTY PURPOSE EXTRA_ARGS PKGCONFIG PKGCONFIG_LIB PKGCONFIG_VER_COMPARISON)
set(multiValueArgs REQUIRED_BY PRIVATE_FOR COMPONENTS OPTIONAL_COMPONENTS)
#------------------------------------
# Parse the arguments
_ign_cmake_parse_arguments(ign_find_package "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
#------------------------------------
# Construct the arguments to pass to find_package
set(${PACKAGE_NAME}_find_package_args ${PACKAGE_NAME})
if(ign_find_package_VERSION)
list(APPEND ${PACKAGE_NAME}_find_package_args ${ign_find_package_VERSION})
endif()
if(ign_find_package_QUIET)
list(APPEND ${PACKAGE_NAME}_find_package_args QUIET)
endif()
if(ign_find_package_EXACT)
list(APPEND ${PACKAGE_NAME}_find_package_args EXACT)
endif()
if(ign_find_package_COMPONENTS)
list(APPEND ${PACKAGE_NAME}_find_package_args COMPONENTS ${ign_find_package_COMPONENTS})
endif()
if(ign_find_package_OPTIONAL_COMPONENTS)
list(APPEND ${PACKAGE_NAME}_find_package_args OPTIONAL_COMPONENTS ${ign_find_package_OPTIONAL_COMPONENTS})
endif()
if(ign_find_package_EXTRA_ARGS)
list(APPEND ${PACKAGE_NAME}_find_package_args ${ign_find_package_EXTRA_ARGS})
endif()
#------------------------------------
# Figure out which name to print
if(ign_find_package_PRETTY)
set(${PACKAGE_NAME}_pretty ${ign_find_package_PRETTY})
else()
set(${PACKAGE_NAME}_pretty ${PACKAGE_NAME})
endif()
#------------------------------------
# Call find_package with the provided arguments
find_package(${${PACKAGE_NAME}_find_package_args})
if(${PACKAGE_NAME}_FOUND)
message(STATUS "Looking for ${${PACKAGE_NAME}_pretty} - found\n")
else()
message(STATUS "Looking for ${${PACKAGE_NAME}_pretty} - not found\n")
#------------------------------------
# Construct the warning/error message to produce
set(${PACKAGE_NAME}_msg "Missing dependency [${${PACKAGE_NAME}_pretty}]")
if(ign_find_package_COMPONENTS)
ign_list_to_string(comp_str ign_find_package_COMPONENTS DELIM ", ")
set(${PACKAGE_NAME}_msg "${${PACKAGE_NAME}_msg} (Components: ${comp_str})")
endif()
if(DEFINED ign_find_package_PURPOSE)
set(${PACKAGE_NAME}_msg "${${PACKAGE_NAME}_msg} - ${ign_find_package_PURPOSE}")
endif()
#------------------------------------
# If the package is unavailable, tell the user.
if(ign_find_package_REQUIRED)
# If it was required by the project, we will create an error.
ign_build_error(${${PACKAGE_NAME}_msg})
elseif(ign_find_package_REQUIRED_BY)
foreach(component ${ign_find_package_REQUIRED_BY})
if(NOT SKIP_${component})
# Otherwise, if it was only required by some of the components, create
# a warning about which components will not be available, unless the
# user explicitly requested that it be skipped
ign_build_warning("Skipping component [${component}]: ${${PACKAGE_NAME}_msg}.\n ^~~~~ Set SKIP_${component}=true in cmake to suppress this warning.\n ")
# Create a variable to indicate that we need to skip the component
set(INTERNAL_SKIP_${component} true)
# Track the missing dependencies
ign_string_append(${component}_MISSING_DEPS "${${PACKAGE_NAME}_pretty}" DELIM ", ")
endif()
endforeach()
else()
if(NOT ign_find_package_QUIET)
ign_build_warning(${${PACKAGE_NAME}_msg})
endif()
endif()
endif()
#------------------------------------
# Add this package to the list of dependencies that will be inserted into the
# find-config file, unless the invoker specifies that it should not be added.
# Also, add this package or library as an entry to the pkgconfig file that we
# will produce for our project.
if( ${PACKAGE_NAME}_FOUND
AND (ign_find_package_REQUIRED OR ign_find_package_REQUIRED_BY)
AND NOT ign_find_package_BUILD_ONLY)
# Set up the arguments we want to pass to the find_dependency invokation for
# our ignition project. We always need to pass the name of the dependency.
#
# NOTE: We escape the dollar signs because we want those variable
# evaluations to be a part of the string that we produce. It is going
# to be put into a *-config.cmake file. Those variables determine
# whether the find_package(~) call will be REQUIRED and/or QUIET.
#
# TODO: When we migrate to cmake-3.9+, this can be removed because calling
# find_dependency(~) will automatically forward these properties.
set(${PACKAGE_NAME}_dependency_args "${PACKAGE_NAME}")
# If a version is provided here, we should pass that as well.
if(ign_find_package_VERSION)
ign_string_append(${PACKAGE_NAME}_dependency_args ${ign_find_package_VERSION})
endif()
# If we have specified the exact version, we should provide that as well.
if(ign_find_package_EXACT)
ign_string_append(${PACKAGE_NAME}_dependency_args EXACT)
endif()
# NOTE (MXG): 7 seems to be the number of escapes required to get
# "${ign_package_required}" and "${ign_package_quiet}" to show up correctly
# as strings in the final config-file outputs. It is unclear to me why the
# escapes get collapsed exactly three times, so it is possible that any
# changes to this script could cause a different number of escapes to be
# necessary. Please use caution when modifying this script.
ign_string_append(${PACKAGE_NAME}_dependency_args "\\\\\\\${ign_package_quiet} \\\\\\\${ign_package_required}")
# If we have specified components of the dependency, mention those.
if(ign_find_package_COMPONENTS)
ign_string_append(${PACKAGE_NAME}_dependency_args "COMPONENTS ${ign_find_package_COMPONENTS}")
endif()
# If there are any additional arguments for the find_package(~) command,
# forward them along.
if(ign_find_package_EXTRA_ARGS)
ign_string_append(${PACKAGE_NAME}_dependency_args "${ign_find_package_EXTRA_ARGS}")
endif()
# TODO: When we migrate to cmake-3.9+ bring back find_dependency(~) because
# at that point it will be able to support COMPONENTS and EXTRA_ARGS
# set(${PACKAGE_NAME}_find_dependency "find_dependency(${${PACKAGE_NAME}_dependency_args})")
set(${PACKAGE_NAME}_find_dependency "find_package(${${PACKAGE_NAME}_dependency_args})")
if(ign_find_package_REQUIRED)
# If this is REQUIRED, add it to PROJECT_CMAKE_DEPENDENCIES
ign_string_append(PROJECT_CMAKE_DEPENDENCIES "${${PACKAGE_NAME}_find_dependency}" DELIM "\n")
endif()
if(ign_find_package_REQUIRED_BY)
# Identify which components are privately requiring this package
foreach(component ${ign_find_package_PRIVATE_FOR})
set(${component}_${PACKAGE_NAME}_PRIVATE true)
endforeach()
# If this is required by some components, add it to the
# ${component}_CMAKE_DEPENDENCIES variables that are specific to those
# componenets
foreach(component ${ign_find_package_REQUIRED_BY})
if(NOT ${component}_${PACKAGE_NAME}_PRIVATE)
ign_string_append(${component}_CMAKE_DEPENDENCIES "${${PACKAGE_NAME}_find_dependency}" DELIM "\n")
endif()
endforeach()
endif()
#------------------------------------
# Add this library or project to its relevant pkgconfig entry, unless we
# have been explicitly instructed to ignore it.
if(NOT ign_find_package_PKGCONFIG_IGNORE)
# Here we will set up the pkgconfig entry for this package. Ordinarily,
# these variables should be set by the ign-cmake custom find-module for
# the package which should use ign_pkg_check_modules[_quiet] or
# ign_pkg_config_library_entry. However, that will not be performed by
# third-party dependencies that provide their own find-module or their own
# cmake config-module. Therefore, we provide the option of specifying
# pkgconfig information through the call to ign_find_package. This also
# allows callers of ign_find_package(~) to overwrite the default
# pkg-config entry that gets generated by the ign-cmake find-modules.
# If the caller has specified the arguments PKGCONFIG_LIB or PKGCONFIG,
# then we will overwrite these pkgconfig variables with the information
# provided by the caller.
if(ign_find_package_PKGCONFIG_LIB)
# Libraries must be prepended with -l
set(${PACKAGE_NAME}_PKGCONFIG_ENTRY "-l${ign_find_package_PKGCONFIG_LIB}")
set(${PACKAGE_NAME}_PKGCONFIG_TYPE PKGCONFIG_LIBS)
elseif(ign_find_package_PKGCONFIG)
# Modules (a.k.a. packages) can just be specified by their package
# name without any prefixes like -l
set(${PACKAGE_NAME}_PKGCONFIG_ENTRY "${ign_find_package_PKGCONFIG}")
set(${PACKAGE_NAME}_PKGCONFIG_TYPE PKGCONFIG_REQUIRES)
# Add the version requirements to the entry.
if(ign_find_package_VERSION)
# Use equivalency by default
set(comparison "=")
# If the caller has specified a version comparison operator, use that
# instead of equivalency.
if(ign_find_package_PKGCONFIG_VER_COMPARISON)
set(comparison ${ign_find_package_PKGCONFIG_VER_COMPARISON})
endif()
# Append the comparison and the version onto the pkgconfig entry
set(${PACKAGE_NAME}_PKGCONFIG_ENTRY "${${PACKAGE_NAME}_PKGCONFIG_ENTRY} ${comparison} ${ign_find_package_VERSION}")
endif()
endif()
if(NOT ${PACKAGE_NAME}_PKGCONFIG_ENTRY)
# The find-module has not provided a default pkg-config entry for this
# package, and the caller of ign_find_package(~) has not explicitly
# provided pkg-config information. The caller has also not specified
# PKGCONFIG_IGNORE. This means that the requirements of this package
# will be unintentionally omitted from the auto-generated
# ignition-<project>.pc file. This is probably an oversight in our build
# system scripts, so we will emit a warning about this.
message(AUTHOR_WARNING
" -- THIS MESSAGE IS INTENDED FOR IGNITION-${IGN_DESIGNATION_UPPER} AUTHORS --\n"
" (IF YOU SEE THIS, PLEASE REPORT IT)\n"
"Could not find pkg-config information for ${PACKAGE_NAME}. "
"It was not provided by the find-module for the package, nor was it "
"explicitly passed into the call to ign_find_package(~). This is "
"most likely an error in this project's use of ign-cmake.")
else()
# We have pkg-config information for this package
if(ign_find_package_REQUIRED)
if(ign_find_package_PRIVATE)
# If this is a private library or module, use the _PRIVATE suffix
set(PROJECT_${PACKAGE_NAME}_PKGCONFIG_TYPE ${${PACKAGE_NAME}_PKGCONFIG_TYPE}_PRIVATE)
else()
# Otherwise, use the plain type
set(PROJECT_${PACKAGE_NAME}_PKGCONFIG_TYPE ${${PACKAGE_NAME}_PKGCONFIG_TYPE})
endif()
# Append the entry as a string onto the project-wide variable for
# whichever requirement type we selected
ign_string_append(PROJECT_${PROJECT_${PACKAGE_NAME}_PKGCONFIG_TYPE} ${${PACKAGE_NAME}_PKGCONFIG_ENTRY})
endif()
if(ign_find_package_REQUIRED_BY)
# For each of the components that requires this package, append its
# entry as a string onto the component-specific variable for whichever
# requirement type we selected
foreach(component ${ign_find_package_REQUIRED_BY})
if(${component}_${PACKAGE_NAME}_PRIVATE)
# If this is a private library or module, use the _PRIVATE suffix
set(${component}_${PACKAGE_NAME}_PKGCONFIG_TYPE ${component}_${${PACKAGE_NAME}_PKGCONFIG_TYPE}_PRIVATE)
else()
# Otherwise, use the plain type
set(${component}_${PACKAGE_NAME}_PKGCONFIG_TYPE ${component}_${${PACKAGE_NAME}_PKGCONFIG_TYPE})
endif()
# Append the entry as a string onto the component-specific variable
# for whichever required type we selected
ign_string_append(${${component}_${PACKAGE_NAME}_PKGCONFIG_TYPE} ${${PACKAGE_NAME}_PKGCONFIG_ENTRY})
endforeach()
endif()
endif()
endif()
endif()
endmacro()
#################################################
# ign_string_append(<output_var> <value_to_append> [DELIM <delimiter>])
#
# <output_var>: The name of the string variable that should be appended to
#
# <value_to_append>: The value that should be appended to the string
#
# [DELIM]: Specify a delimiter to separate the contents with. Default value is a
# space
#
# Macro to append a value to a string
macro(ign_string_append output_var val)
#------------------------------------
# Define the expected arguments
# NOTE: options cannot be set to PARENT_SCOPE alone, so we put it explicitly
# into cmake_parse_arguments(~). We use a semicolon to concatenate it with
# this options variable, so all other options should be specified here.
set(options)
set(oneValueArgs DELIM)
set(multiValueArgs) # We are not using multiValueArgs yet
#------------------------------------
# Parse the arguments
_ign_cmake_parse_arguments(ign_string_append "PARENT_SCOPE;${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(ign_string_append_DELIM)
set(delim "${ign_string_append_DELIM}")
else()
set(delim " ")
endif()
if( (NOT ${output_var}) OR (${output_var} STREQUAL "") )
# If ${output_var} is blank, just set it to equal ${val}
set(${output_var} "${val}")
else()
# If ${output_var} already has a value in it, append ${val} with the
# delimiter in-between.
set(${output_var} "${${output_var}}${delim}${val}")
endif()
if(ign_string_append_PARENT_SCOPE)
set(${output_var} "${${output_var}}" PARENT_SCOPE)
endif()
endmacro()
#################################################
# Macro to turn a list into a string
macro(ign_list_to_string _output _input_list)
set(${_output})
foreach(_item ${${_input_list}})
# Append each item, and forward any extra options to ign_string_append, such
# as DELIM or PARENT_SCOPE
ign_string_append(${_output} "${_item}" ${ARGN})
endforeach(_item)
endmacro()
#################################################
# ign_get_sources_and_unittests(<lib_srcs> <tests>)
#
# Grab all the files ending in "*.cc" from either the "src/" subdirectory or the
# current subdirectory if "src/" does not exist. They will be collated into
# library source files <lib_sources_var> and unittest source files <tests_var>.
#
# These output variables can be consumed directly by ign_create_core_library(~),
# ign_add_component(~), ign_build_tests(~), and ign_build_executables(~).
function(ign_get_libsources_and_unittests lib_sources_var tests_var)
# Glob all the source files
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/src)
# Prefer files in the src/ subdirectory
file(GLOB source_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "src/*.cc")
file(GLOB test_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "src/*_TEST.cc")
else()
# If src/ doesn't exist, then use the current directory
file(GLOB source_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "*.cc")
file(GLOB test_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "*_TEST.cc")
endif()
# Sort the files alphabetically
if(source_files)
list(SORT source_files)
endif()
if(test_files)
list(SORT test_files)
endif()
# Initialize the test list
set(tests)
# Remove the unit tests from the list of source files
foreach(test_file ${test_files})
# Remove from the source_files list.
list(REMOVE_ITEM source_files ${test_file})
# Append to the list of tests.
list(APPEND tests ${test_file})
endforeach()
# Return the lists that have been created.
set(${lib_sources_var} ${source_files} PARENT_SCOPE)
set(${tests_var} ${tests} PARENT_SCOPE)
endfunction()
#################################################
# ign_get_sources(<sources>)
#
# From the current directory, grab all the source files and place them into
# <sources>. Remove their paths to make them suitable for passing into
# ign_add_[library/tests].
function(ign_get_sources sources_var)
# GLOB all the source files
file(GLOB source_files "*.cc")
list(SORT source_files)
# Initialize this list
set(sources)
foreach(source_file ${source_files})
# Remove the path from the source file and append it the list of soures
get_filename_component(source ${source_file} NAME)
list(APPEND sources ${source})
endforeach()
# Return the list that has been created
set(${sources_var} ${sources} PARENT_SCOPE)
endfunction()
#################################################
# ign_install_all_headers(
# [EXCLUDE_FILES <excluded_headers>]
# [EXCLUDE_DIRS <dirs>]
# [GENERATED_HEADERS <headers>]
# [COMPONENT] <component>)
#
# From the current directory, install all header files, including files from all
# subdirectories (recursively). You can optionally specify directories or files
# to exclude from installation (the names must be provided relative to the current
# source directory).
#
# This will accept all files ending in *.h and *.hh. You may append an
# additional suffix (like .old or .backup) to prevent a file from being included.
#
# GENERATED_HEADERS should be generated headers which should be included by
# ${IGN_DESIGNATION}.hh. This will only add them to the header, it will not
# generate or install them.
#
# This will also run configure_file on ign_auto_headers.hh.in and config.hh.in
# and install them. This will NOT install any other files or directories that
# appear in the ${CMAKE_CURRENT_BINARY_DIR}.
#
# If the COMPONENT option is specified, this will skip over configuring a
# config.hh file since it would be redundant with the core library.
#
function(ign_install_all_headers)
#------------------------------------
# Define the expected arguments
set(options)
set(oneValueArgs COMPONENT) # We are not using oneValueArgs yet
set(multiValueArgs EXCLUDE_FILES EXCLUDE_DIRS GENERATED_HEADERS)
#------------------------------------
# Parse the arguments
_ign_cmake_parse_arguments(ign_install_all_headers "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
#------------------------------------
# Build the list of directories
file(GLOB_RECURSE all_files LIST_DIRECTORIES TRUE RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*")
list(SORT all_files)
set(directories)
foreach(f ${all_files})
# Check if this file is a directory
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${f})
# Check if it is in the list of excluded directories
list(FIND ign_install_all_headers_EXCLUDE_DIRS ${f} f_index)
set(append_file TRUE)
foreach(subdir ${ign_install_all_headers_EXCLUDE_DIRS})
# Check if ${f} contains ${subdir} as a substring
string(FIND ${f} ${subdir} pos)
# If ${subdir} is a substring of ${f} at the very first position, then
# we should not include anything from this directory. This makes sure
# that if a user specifies "EXCLUDE_DIRS foo" we will also exclude
# the directories "foo/bar/..." and so on. We will not, however, exclude
# a directory named "bar/foo/".
if(${pos} EQUAL 0)
set(append_file FALSE)
break()
endif()
endforeach()
if(append_file)
list(APPEND directories ${f})
endif()
endif()
endforeach()
# Append the current directory to the list
list(APPEND directories ".")
#------------------------------------
# Install all the non-excluded header directories along with all of their
# non-excluded headers
foreach(dir ${directories})
# GLOB all the header files in dir
file(GLOB headers RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/*.h" "${dir}/*.hh" "${dir}/*.hpp")
list(SORT headers)
# Remove the excluded headers
if(headers)
foreach(exclude ${ign_install_all_headers_EXCLUDE_FILES})
list(REMOVE_ITEM headers ${exclude})
endforeach()
endif()
# Add each header, prefixed by its directory, to the auto headers variable
foreach(header ${headers})
set(ign_headers "${ign_headers}#include <ignition/${IGN_DESIGNATION}/${header}>\n")
endforeach()
if("." STREQUAL ${dir})
set(destination "${IGN_INCLUDE_INSTALL_DIR_FULL}/ignition/${IGN_DESIGNATION}")
else()
set(destination "${IGN_INCLUDE_INSTALL_DIR_FULL}/ignition/${IGN_DESIGNATION}/${dir}")
endif()
install(
FILES ${headers}
DESTINATION ${destination}
COMPONENT headers)
endforeach()
# Add generated headers to the list of includes
foreach(header ${ign_install_all_headers_GENERATED_HEADERS})
set(ign_headers "${ign_headers}#include <ignition/${IGN_DESIGNATION}/${header}>\n")
endforeach()
if(ign_install_all_headers_COMPONENT)
set(component_name ${ign_install_all_headers_COMPONENT})
# Define the install directory for the component meta header
set(meta_header_install_dir ${IGN_INCLUDE_INSTALL_DIR_FULL}/ignition/${IGN_DESIGNATION}/${component_name})
# Define the input/output of the configuration for the component "master" header
set(master_header_in ${IGNITION_CMAKE_DIR}/ign_auto_headers.hh.in)
set(master_header_out ${CMAKE_CURRENT_BINARY_DIR}/${component_name}.hh)
else()
# Define the install directory for the core master meta header
set(meta_header_install_dir ${IGN_INCLUDE_INSTALL_DIR_FULL}/ignition/${IGN_DESIGNATION})
# Define the input/output of the configuration for the core "master" header
set(master_header_in ${IGNITION_CMAKE_DIR}/ign_auto_headers.hh.in)
set(master_header_out ${CMAKE_CURRENT_BINARY_DIR}/../${IGN_DESIGNATION}.hh)
endif()
# Generate the "master" header that includes all of the headers
configure_file(${master_header_in} ${master_header_out})
# Install the "master" header
install(
FILES ${master_header_out}
DESTINATION ${meta_header_install_dir}/..
COMPONENT headers)
# Define the input/output of the configuration for the "config" header
set(config_header_in ${CMAKE_CURRENT_SOURCE_DIR}/config.hh.in)
set(config_header_out ${CMAKE_CURRENT_BINARY_DIR}/config.hh)
if(NOT ign_install_all_headers_COMPONENT)
# Produce an error if the config file is missing
#
# TODO: Maybe we should have a generic config.hh.in file that we fall back
# on if the project does not have one for itself?
if(NOT EXISTS ${config_header_in})
message(FATAL_ERROR
"Developer error: You are missing the file [${config_header_in}]! "
"Did you forget to move it from your project's cmake directory while "
"migrating to the use of ignition-cmake?")
endif()
# Generate the "config" header that describes our project configuration
configure_file(${config_header_in} ${config_header_out})
# Install the "config" header
install(
FILES ${config_header_out}
DESTINATION ${meta_header_install_dir}
COMPONENT headers)
endif()
endfunction()
#################################################
# ign_build_error macro
macro(ign_build_error)
foreach(str ${ARGN})
set(msg "\t${str}")
list(APPEND build_errors ${msg})
endforeach()
endmacro(ign_build_error)
#################################################
# ign_build_warning macro
macro(ign_build_warning)
foreach(str ${ARGN})
list(APPEND build_warnings "${str}")
endforeach(str ${ARGN})
endmacro(ign_build_warning)
#################################################
macro(ign_add_library lib_target_name)
message(FATAL_ERROR
"ign_add_library(<target_name> <sources>) is deprecated. Instead, use "
"ign_create_core_library(SOURCES <sources>). It will determine the library "
"target name automatically from the project name. To add a component "
"library, use ign_add_component(~). Be sure to pass the CXX_STANDARD "
"argument to these functions in order to set the C++ standard that they "
"require.")
ign_create_core_library(SOURCES ${ARGN})
endmacro()
#################################################
# _ign_check_known_cxx_standards(<11|14|17>)
#
# Creates a fatal error if the variable passed in does not represent a supported
# version of the C++ standard.
#
# NOTE: This function is meant for internal ign-cmake use
#
function(_ign_check_known_cxx_standards standard)
list(FIND IGN_KNOWN_CXX_STANDARDS ${standard} known)
if(${known} EQUAL -1)
message(FATAL_ERROR
"You have specified an unsupported standard: ${standard}. "
"Accepted values are: ${IGN_KNOWN_CXX_STANDARDS}.")
endif()
endfunction()
#################################################
# _ign_handle_cxx_standard(<function_prefix>
# <target_name>
# <pkgconfig_cflags_variable>)
#
# Handles the C++ standard argument for ign_create_core_library(~) and
# ign_add_component(~).
#
# NOTE: This is only meant for internal ign-cmake use.
#
macro(_ign_handle_cxx_standard prefix target pkgconfig_cflags)
if(${prefix}_CXX_STANDARD)
_ign_check_known_cxx_standards(${${prefix}_CXX_STANDARD})
endif()
if(${prefix}_PRIVATE_CXX_STANDARD)
_ign_check_known_cxx_standards(${${prefix}_PRIVATE_CXX_STANDARD})
endif()
if(${prefix}_INTERFACE_CXX_STANDARD)
_ign_check_known_cxx_standards(${${prefix}_INTERFACE_CXX_STANDARD})
endif()
if(${prefix}_CXX_STANDARD
AND (${prefix}_PRIVATE_CXX_STANDARD
OR ${prefix}_INTERFACE_CXX_STANDARD))
message(FATAL_ERROR
"If CXX_STANDARD has been specified, then you are not allowed to specify "
"PRIVATE_CXX_STANDARD or INTERFACE_CXX_STANDARD. Please choose to either "
"specify CXX_STANDARD alone, or else specify some combination of "
"PRIVATE_CXX_STANDARD and INTERFACE_CXX_STANDARD")
endif()
if(${prefix}_CXX_STANDARD)
set(${prefix}_INTERFACE_CXX_STANDARD ${${prefix}_CXX_STANDARD})
set(${prefix}_PRIVATE_CXX_STANDARD ${${prefix}_CXX_STANDARD})
endif()
if(${prefix}_INTERFACE_CXX_STANDARD)
target_compile_features(${target} INTERFACE ${IGN_CXX_${${prefix}_INTERFACE_CXX_STANDARD}_FEATURES})
ign_string_append(${pkgconfig_cflags} "-std=c++${${prefix}_INTERFACE_CXX_STANDARD}")
endif()
if(${prefix}_PRIVATE_CXX_STANDARD)
target_compile_features(${target} PRIVATE ${IGN_CXX_${${prefix}_PRIVATE_CXX_STANDARD}_FEATURES})
endif()
endmacro()
#################################################
# ign_create_core_library(SOURCES <sources>
# [CXX_STANDARD <11|14|17>]
# [PRIVATE_CXX_STANDARD <11|14|17>]
# [INTERFACE_CXX_STANDARD <11|14|17>]
# [GET_TARGET_NAME <output_var>])
#
# This function will produce the "core" library for your project. There is no
# need to specify a name for the library, because that will be determined by
# your project information.
#
# SOURCES: Required. Specify the source files that will be used to generate the
# library.
#
# [GET_TARGET_NAME]: Optional. The variable that follows this argument will be
# set to the library target name that gets produced by this
# function. The target name will always be
# ${PROJECT_LIBRARY_TARGET_NAME}.
#
# If you need a specific C++ standard, you must also specify it in this
# function in order to ensure that your library's target properties get set
# correctly. The following is a breakdown of your choices:
#
# [CXX_STANDARD]: This library must compile using the specified standard, and so
# must any libraries which link to it.
#
# [PRIVATE_CXX_STANDARD]: This library must compile using the specified standard,
# but libraries which link to it do not need to.
#
# [INTERFACE_CXX_STANDARD]: Any libraries which link to this library must compile
# with the specified standard.
#
# Most often, you will want to use CXX_STANDARD, but there may be cases in which
# you want a finer degree of control. If your library must compile with a
# different standard than what is required by dependent libraries, then you can
# specify both PRIVATE_CXX_STANDARD and INTERFACE_CXX_STANDARD without any
# conflict. However, both of those arguments conflict with CXX_STANDARD, so you
# are not allowed to use either of them if you use the CXX_STANDARD argument.
#
function(ign_create_core_library)
#------------------------------------
# Define the expected arguments
set(options INTERFACE)
set(oneValueArgs INCLUDE_SUBDIR CXX_STANDARD PRIVATE_CXX_STANDARD INTERFACE_CXX_STANDARD GET_TARGET_NAME)
set(multiValueArgs SOURCES)
#------------------------------------
# Parse the arguments
cmake_parse_arguments(ign_create_core_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(ign_create_core_library_SOURCES)
set(sources ${ign_create_core_library_SOURCES})
elseif(NOT ign_create_core_library_INTERFACE)
message(FATAL_ERROR "You must specify SOURCES for ign_create_core_library(~)!")
endif()
if(ign_create_core_library_INTERFACE)
set(interface_option INTERFACE)
set(property_type INTERFACE)
else()
set(interface_option) # Intentionally blank
set(property_type PUBLIC)
endif()
#------------------------------------
# Create the target for the core library, and configure it to be installed
_ign_add_library_or_component(
LIB_NAME ${PROJECT_LIBRARY_TARGET_NAME}
INCLUDE_DIR "ignition/${IGN_DESIGNATION_LOWER}"
EXPORT_BASE IGNITION_${IGN_DESIGNATION_UPPER}
SOURCES ${sources}
${interface_option})
# These generator expressions are necessary for multi-configuration generators
# such as MSVC on Windows. They also ensure that our target exports its
# headers correctly
target_include_directories(${PROJECT_LIBRARY_TARGET_NAME}
${property_type}
# This is the publicly installed headers directory.
"$<INSTALL_INTERFACE:${IGN_INCLUDE_INSTALL_DIR_FULL}>"
# This is the in-build version of the core library headers directory.
# Generated headers for the core library get placed here.
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
# Generated headers for the core library might also get placed here.
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/core/include>")
# We explicitly create these directories to avoid false-flag compiler warnings
file(MAKE_DIRECTORY
"${PROJECT_BINARY_DIR}/include"
"${PROJECT_BINARY_DIR}/core/include")
if(EXISTS "${PROJECT_SOURCE_DIR}/include")
target_include_directories(${PROJECT_LIBRARY_TARGET_NAME}
${property_type}
# This is the build directory version of the headers. When exporting the
# target, this will not be included, because it is tied to the build
# interface instead of the install interface.
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
endif()
if(EXISTS "${PROJECT_SOURCE_DIR}/core/include")
target_include_directories(${PROJECT_LIBRARY_TARGET_NAME}
${property_type}
# This is the include directories for projects that put the core library
# contents into its own subdirectory.
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/core/include>")
endif()
#------------------------------------
# Adjust variables if a specific C++ standard was requested
_ign_handle_cxx_standard(ign_create_core_library
${PROJECT_LIBRARY_TARGET_NAME} PROJECT_PKGCONFIG_CFLAGS)
#------------------------------------
# Handle cmake and pkgconfig packaging
if(ign_create_core_library_INTERFACE)
set(project_pkgconfig_core_lib) # Intentionally blank
else()
set(project_pkgconfig_core_lib "-l${PROJECT_NAME_LOWER}")
endif()
# Export and install the core library's cmake target and package information
_ign_create_cmake_package()
# Generate and install the core library's pkgconfig information
_ign_create_pkgconfig()