1# Copyright (c) Monetra Technologies LLC
2# SPDX-License-Identifier: MIT
3
4# EnableWarnings.cmake
5#
6# Checks for and turns on a large number of warning C flags.
7#
8# Adds the following helper functions:
9#
10#	remove_warnings(... list of warnings ...)
11#		Turn off given list of individual warnings for all targets and subdirectories added after this.
12#
13#   remove_all_warnings()
14#		Remove all warning flags, add -w to suppress built-in warnings.
15#
16#   remove_all_warnings_from_targets(... list of targets ...)
17#       Suppress warnings for the given targets only.
18#
19#   push_warnings()
20#		Save current warning flags by pushing them onto an internal stack. Note that modifications to the internal
21#		stack are only visible in the current CMakeLists.txt file and its children.
22#
23#       Note: changing warning flags multiple times in the same directory only affects add_subdirectory() calls.
24#             Targets in the directory will always use the warning flags in effect at the end of the CMakeLists.txt
25#             file - this is due to really weird and annoying legacy behavior of CMAKE_C_FLAGS.
26#
27#   pop_warnings()
28#       Restore the last set of flags that were saved with push_warnings(). Note that modifications to the internal
29#		stack are only visible in the current CMakeLists.txt file and its children.
30#
31
32if (_internal_enable_warnings_already_run)
33	return()
34endif ()
35set(_internal_enable_warnings_already_run TRUE)
36
37include(CheckCCompilerFlag)
38include(CheckCXXCompilerFlag)
39
40get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
41
42# internal helper: _int_enable_warnings_set_flags_ex(langs_var configs_var [warnings flags])
43function(_int_enable_warnings_set_flags_ex langs_var configs_var)
44	if (NOT ARGN)
45		return()
46	endif ()
47
48	if (NOT ${configs_var})
49		set(${configs_var} "NONE")
50	endif ()
51	string(TOUPPER "${${configs_var}}" ${configs_var})
52
53	foreach(_flag ${ARGN})
54		string(MAKE_C_IDENTIFIER "HAVE_${_flag}" varname)
55
56		if ("C" IN_LIST ${langs_var})
57			check_c_compiler_flag(${_flag} ${varname})
58			if (${varname})
59				foreach (config IN LISTS ${configs_var})
60					if (config STREQUAL "NONE")
61						set(config)
62					else ()
63						set(config "_${config}")
64					endif ()
65					string(APPEND CMAKE_C_FLAGS${config} " ${_flag}")
66				endforeach ()
67			endif ()
68		endif ()
69
70		if ("CXX" IN_LIST ${langs_var})
71			string(APPEND varname "_CXX")
72			check_cxx_compiler_flag(${_flag} ${varname})
73			if (${varname})
74				foreach (config IN LISTS ${configs_var})
75					if (config STREQUAL "NONE")
76						set(config)
77					else ()
78						set(config "_${config}")
79					endif ()
80					string(APPEND CMAKE_CXX_FLAGS${config} " ${_flag}")
81				endforeach ()
82			endif ()
83		endif ()
84	endforeach()
85
86	foreach(lang C CXX)
87		foreach (config IN LISTS ${configs_var})
88			string(TOUPPER "${config}" config)
89			if (config STREQUAL "NONE")
90				set(config)
91			else ()
92				set(config "_${config}")
93			endif ()
94			string(STRIP "${CMAKE_${lang}_FLAGS${config}}" CMAKE_${lang}_FLAGS${config})
95			set(CMAKE_${lang}_FLAGS${config} "${CMAKE_${lang}_FLAGS${config}}" PARENT_SCOPE)
96		endforeach ()
97	endforeach()
98endfunction()
99
100# internal helper: _int_enable_warnings_set_flags(langs_var [warnings flags])
101macro(_int_enable_warnings_set_flags langs_var)
102	set(configs "NONE")
103	_int_enable_warnings_set_flags_ex(${langs_var} configs ${ARGN})
104endmacro()
105
106set(_flags_C)
107set(_flags_CXX)
108set(_debug_flags_C)
109set(_debug_flags_CXX)
110
111if (MSVC)
112	# Visual Studio uses a completely different nomenclature for warnings than gcc/mingw/clang, so none of the
113	# "-W[name]" warnings will work.
114
115	# W4 would be better but it produces unnecessary warnings like:
116	# *  warning C4706: assignment within conditional expression
117	#     Triggered when doing "while(1)"
118	# * warning C4115: 'timeval' : named type definition in parentheses
119	# * warning C4201: nonstandard extension used : nameless struct/union
120	#     Triggered by system includes (commctrl.h, shtypes.h, Shlobj.h)
121	set(_flags
122		/W3
123		/we4013 # Treat "function undefined, assuming extern returning int" warning as an error. https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4013
124	)
125
126	list(APPEND _flags_C   ${_flags})
127	list(APPEND _flags_CXX ${_flags})
128
129elseif (CMAKE_C_COMPILER_ID MATCHES "Intel")
130	# Intel's compiler warning flags are more like Visual Studio than GCC, though the numbers aren't the same.
131	set(_flags
132		# Use warning level 3, quite wordy.
133		-w3
134		# Disable warnings we don't care about (add more as they are encountered).
135		-wd383    # Spammy warning about initializing from a temporary object in C++ (which is done all the time ...).
136		-wd11074  # Diagnostic related to inlining.
137		-wd11076  # Diagnostic related to inlining.
138	)
139
140	list(APPEND _flags_C   ${_flags})
141	list(APPEND _flags_CXX ${_flags})
142
143elseif (CMAKE_C_COMPILER_ID MATCHES "XL")
144	set (_flags
145		-qwarn64
146		-qformat=all
147		-qflag=i:i
148	)
149	list(APPEND _flags_C   ${_flags})
150	list(APPEND _flags_CXX ${_flags})
151
152else ()
153	# If we're compiling with GCC / Clang / MinGW (or anything else besides Visual Studio or Intel):
154	# C Flags:
155	list(APPEND _flags_C
156		-Wall
157		-Wextra
158
159		# Enable additional warnings not covered by Wall and Wextra.
160		-Wcast-align
161		-Wconversion
162		-Wdeclaration-after-statement
163		-Wdouble-promotion
164		-Wfloat-equal
165		-Wformat-security
166		-Winit-self
167		-Wjump-misses-init
168		-Wlogical-op
169		-Wmissing-braces
170		-Wmissing-declarations
171		-Wmissing-format-attribute
172		-Wmissing-include-dirs
173		-Wmissing-prototypes
174		-Wnested-externs
175		-Wno-coverage-mismatch
176		-Wold-style-definition
177		-Wpacked
178		-Wpointer-arith
179		-Wredundant-decls
180		-Wshadow
181		-Wsign-conversion
182		-Wstrict-overflow
183		-Wstrict-prototypes
184		-Wtrampolines
185		-Wundef
186		-Wunused
187		-Wvariadic-macros
188		-Wvla
189		-Wwrite-strings
190
191		# On Windows MinGW I think implicit fallthrough enabled by -Wextra must not default to 3
192		-Wimplicit-fallthrough=3
193
194		# Treat implicit variable typing and implicit function declarations as errors.
195		-Werror=implicit-int
196		-Werror=implicit-function-declaration
197
198		# Make MacOSX honor -mmacosx-version-min
199		-Werror=partial-availability
200
201		# Some clang versions might warn if an argument like "-I/path/to/headers" is unused,
202		# silence these.
203		-Qunused-arguments
204	)
205
206	# C++ flags:
207	list(APPEND _flags_CXX
208		-Wall
209		-Wextra
210
211		# Enable additional warnings not covered by Wall and Wextra.
212		-Wcast-align
213		-Wformat-security
214		-Wmissing-declarations
215		-Wmissing-format-attribute
216		-Wpacked-bitfield-compat
217		-Wredundant-decls
218		-Wvla
219
220		# Turn off unused parameter warnings with C++ (they happen often in C++ and Qt).
221		-Wno-unused-parameter
222
223		# Some clang versions might warn if an argument like "-I/path/to/headers" is unused,
224		# silence these.
225		-Qunused-arguments
226	)
227
228	# Note: when cross-compiling to Windows from Cygwin, the Qt Mingw packages have a bunch of
229	#       noisy type-conversion warnings in headers. So, only enable those warnings if we're
230	#       not building that configuration.
231	if (NOT (WIN32 AND (CMAKE_HOST_SYSTEM_NAME MATCHES "CYGWIN")))
232		list(APPEND _flags_CXX
233			-Wconversion
234			-Wfloat-equal
235			-Wsign-conversion
236		)
237	endif ()
238
239	# Add flags to force colored output even when output is redirected via pipe.
240	if (CMAKE_GENERATOR MATCHES "Ninja")
241		set(color_default TRUE)
242	else ()
243		set(color_default FALSE)
244	endif ()
245	option(FORCE_COLOR "Force compiler to always colorize, even when output is redirected." ${color_default})
246	mark_as_advanced(FORCE FORCE_COLOR)
247	if (FORCE_COLOR)
248		set(_flags
249			-fdiagnostics-color=always # GCC
250			-fcolor-diagnostics        # Clang
251		)
252		list(APPEND _flags_C   ${_flags})
253		list(APPEND _flags_CXX ${_flags})
254	endif ()
255
256	# Add -fno-omit-frame-pointer (and optionally -fno-inline) to make debugging and stack dumps nicer.
257	set(_flags
258		-fno-omit-frame-pointer
259	)
260	option(M_NO_INLINE "Disable function inlining for RelWithDebInfo and Debug configurations?" FALSE)
261	if (M_NO_INLINE)
262		list(APPEND _flags
263			-fno-inline
264		)
265	endif ()
266	list(APPEND _debug_flags_C   ${_flags})
267	list(APPEND _debug_flags_CXX ${_flags})
268endif ()
269
270# Check and set compiler flags.
271set(_debug_configs
272	RelWithDebInfo
273	Debug
274)
275foreach(_lang ${languages})
276	_int_enable_warnings_set_flags(_lang ${_flags_${_lang}})
277	_int_enable_warnings_set_flags_ex(_lang _debug_configs ${_debug_flags_${_lang}})
278
279	# Ensure pure Debug builds are NOT optimized (not possible on Visual Studio).
280	# Any optimization of a Debug build will prevent debuggers like lldb from
281	# fully displaying backtraces and stepping.
282	if (NOT MSVC)
283		set(_config Debug)
284		_int_enable_warnings_set_flags_ex(_lang _config -O0)
285	endif ()
286endforeach()
287
288
289
290# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
291# Helper functions
292
293
294# This function can be called in subdirectories, to prune out warnings that they don't want.
295#  vararg: warning flags to remove from list of enabled warnings. All "no" flags after EXPLICIT_DISABLE
296#          will be added to C flags.
297#
298# Ex.: remove_warnings(-Wall -Wdouble-promotion -Wcomment) prunes those warnings flags from the compile command.
299function(remove_warnings)
300	get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
301	set(langs C)
302	if ("CXX" IN_LIST languages)
303		list(APPEND langs CXX)
304	endif ()
305
306	foreach(lang ${langs})
307		set(toadd)
308		set(in_explicit_disable FALSE)
309		foreach (flag ${ARGN})
310			if (flag STREQUAL "EXPLICIT_DISABLE")
311				set(in_explicit_disable TRUE)
312			elseif (in_explicit_disable)
313				list(APPEND toadd "${flag}")
314			else ()
315				string(REGEX REPLACE "${flag}([ \t]+|$)" "" CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}")
316			endif ()
317		endforeach ()
318		_int_enable_warnings_set_flags(lang ${toadd})
319		string(STRIP "${CMAKE_${lang}_FLAGS}" CMAKE_${lang}_FLAGS)
320		set(CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}" PARENT_SCOPE)
321	endforeach()
322endfunction()
323
324
325# Explicitly suppress all warnings. As long as this flag is the last warning flag, warnings will be
326# suppressed even if earlier flags enabled warnings.
327function(remove_all_warnings)
328	get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
329	set(langs C)
330	if ("CXX" IN_LIST languages)
331		list(APPEND langs CXX)
332	endif ()
333
334	foreach(lang ${langs})
335		string(REGEX REPLACE "[-/][Ww][^ \t]*([ \t]+|$)" "" CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}")
336		if (MSVC)
337			string(APPEND CMAKE_${lang}_FLAGS " /w")
338		else ()
339			string(APPEND CMAKE_${lang}_FLAGS " -w")
340		endif ()
341		string(STRIP "${CMAKE_${lang}_FLAGS}" CMAKE_${lang}_FLAGS)
342		set(CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}" PARENT_SCOPE)
343	endforeach()
344endfunction()
345
346
347function(remove_all_warnings_from_targets)
348	foreach (target ${ARGN})
349		if (MSVC)
350			target_compile_options(${target} PRIVATE "/w")
351		else ()
352			target_compile_options(${target} PRIVATE "-w")
353		endif ()
354	endforeach()
355endfunction()
356
357
358# Save the current warning settings to an internal variable.
359function(push_warnings)
360	get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
361	set(langs C)
362	if ("CXX" IN_LIST languages)
363		list(APPEND langs CXX)
364	endif ()
365
366	foreach(lang ${langs})
367		if (CMAKE_${lang}_FLAGS MATCHES ";")
368			message(AUTHOR_WARNING "Cannot push warnings for ${lang}, CMAKE_${lang}_FLAGS contains semicolons")
369			continue()
370		endif ()
371		# Add current flags to end of internal list.
372		list(APPEND _enable_warnings_internal_${lang}_flags_stack "${CMAKE_${lang}_FLAGS}")
373		# Propagate results up to caller's scope.
374		set(_enable_warnings_internal_${lang}_flags_stack "${_enable_warnings_internal_${lang}_flags_stack}" PARENT_SCOPE)
375	endforeach()
376endfunction()
377
378
379# Restore the current warning settings from an internal variable.
380function(pop_warnings)
381	get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
382	set(langs C)
383	if ("CXX" IN_LIST languages)
384		list(APPEND langs CXX)
385	endif ()
386
387	foreach(lang ${langs})
388		if (NOT _enable_warnings_internal_${lang}_flags_stack)
389			continue()
390		endif ()
391		# Pop flags off of end of list, overwrite current flags with whatever we popped off.
392		list(GET _enable_warnings_internal_${lang}_flags_stack -1 CMAKE_${lang}_FLAGS)
393		list(REMOVE_AT _enable_warnings_internal_${lang}_flags_stack -1)
394		# Propagate results up to caller's scope.
395		set(_enable_warnings_internal_${lang}_flags_stack "${_enable_warnings_internal_${lang}_flags_stack}" PARENT_SCOPE)
396		string(STRIP "${CMAKE_${lang}_FLAGS}" CMAKE_${lang}_FLAGS)
397		set(CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS}" PARENT_SCOPE)
398	endforeach()
399endfunction()
400