Daemon icon indicating copy to clipboard operation
Daemon copied to clipboard

Report what compiler is used to build the game

Open illwieckz opened this issue 2 years ago • 22 comments

Those are old commits that were sitting on my hard drive for a long time. I believe I wrote them when we were investigating the console bug that only happened on clang and clang-based compilers. I assume that enabling PCH with Saigo would now just require to replace the test for NACL with a test for the PNaCl compiler.

The first need I wanted to fulfill was to write in daemon.log the compiler that was used to build the game, in order to give useful information to people dealing with bug reports.

The second need I wanted to fulfill was to know at build time by reading the build log what CMake was really using when I set some options.

While I was at it, I added custom code to recognize custom compilers like PNaCl and Saigo (in fact at the time I had added Wasi recognition but I removed that before pushing because we really don't know yet which compiler we will use for Wasm).

I also added recognition and compatibility with some other compilers I usually use to test the game, which usually catches different warnings in ways that can be useful to debug things or spot errors. This includes Intel ICC and ICX, AMD AOCC, and Zig. This is not meant to add “support” in a way we would claim to support this, we actually don't have to advertise the fact those compilers can build the game, and this does not give us any obligation to fix the compatibility if one day it breaks. This is only useful for testing, the same way I test the game on various CPU and GPU, I test the build on many compilers.

While doing this I spotted some shortcomings like some flags not being the same for the engine if built within Unvanquished repository or from Daemon repository.

I also spotted that we configure engine platform, compiler even when we don't build the engine, even when we will use another compiler for building the vms.

For those two shortcomings I only added comments and some print to tell when some CMake logs are meaningless, because fixing those shortcomings that exist in our CMake code since forever is out of topic of this PR.

If the compiler is unknown, it reports Unknown as compiler name and Unknown as compiler version. It is expected the clang version is appended if the unknown compiler is clang-based (like known ones).

See also:

  • https://github.com/Unvanquished/Unvanquished/pull/2864

illwieckz avatar Dec 03 '23 18:12 illwieckz

When building we get logs like that:

-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: GNU 13.2.0
-- Detected C++ compiler: GNU 13.2.0
-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: Clang 16.0.6
-- Detected C++ compiler: Clang 16.0.6
-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: Intel 2021.10.0.20230609
-- Detected C++ compiler: Intel 2021.10.0.20230609
-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: IntelLLVM 2024.0.0/clang-17.0.0
-- Detected C++ compiler: IntelLLVM 2024.0.0/clang-17.0.0
-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: AOCC 4.0.0-Build#434/clang-14.0.6
-- Detected C++ compiler: AOCC 4.0.0-Build#434/clang-14.0.6
-- Detected architecture: pnacl
-- CMake generator: Unix Makefiles
-- Detected C compiler: PNaCl 4.2.1/clang-3.6.0
-- Detected C++ compiler: PNaCl 4.2.1/clang-3.6.0
-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: AppleClang 13.0.0.13000029/clang-13.0.0 
-- Detected C++ compiler: AppleClang 13.0.0.13000029/clang-13.0.0 
-- Detected architecture: amd64
-- CMake generator: Visual Studio 16 2019
-- Detected C compiler: MSVC 19.29.30153.0 
-- Detected C++ compiler: MSVC 19.29.30153.0

When running the game we get logs like that:

Unvanquished 0.54.0 Linux amd64 AOCC 4.0.0-Build~434/clang-14.0.6 Dec  3 2023
SGame Native Client pnacl PNaCl 4.2.1/clang-3.6.0 Dec  3 2023
CGame Native Client pnacl PNaCl 4.2.1/clang-3.6.0 Dec  3 2023

illwieckz avatar Dec 03 '23 18:12 illwieckz

Actually this don't detect MingW as a variant of GCC:

-- Detected architecture: amd64
-- CMake generator: Ninja
-- Detected C compiler: GNU 9.3.0 
-- Detected C++ compiler: GNU 9.3.0

This may be improved, but this is a good example of how the code is assumed to be safe: in worst case there is less information printed, that's all.

illwieckz avatar Dec 03 '23 18:12 illwieckz

MinGW is now detected too:

-- Detected architecture: amd64
-- CMake generator: Unix Makefiles
-- Detected C compiler: MinGW64 12-posix/gcc-12.0.0 
-- Detected C++ compiler: MinGW64 12-posix/gcc-12.0.0 

illwieckz avatar Dec 03 '23 19:12 illwieckz

I don't find it worth the hassle to have hundreds of lines of code attempting to parse the compiler's version output. Let's compare this with the architecture determining tool:

  • CMake's architecture information is garbage: inconsistent and poorly documented. So better arch info is sorely needed. Whereas CMake does an OK job telling us what brand of compiler we're using.
  • The architecture tool uses compiler macros which are a well-documented and stable interface. Version strings are free-form and might be changed without warning as they are not intended as a machine-readable interface.

Instead of this, I propose just doing $CC --version and capturing the first line, without further parsing. I tried this with various compilers (mostly from godbolt.org):

gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
g++.exe (MinGW-W64 x86_64-ucrt-mcf-seh, built by Brecht Sanders) 13.1.0
clang version 3.6.0 (https://chromium.googlesource.com/a/native_client/pnacl-clang.git 96b3da27dcefc9d152e51cf54280989b2206d789) (https://chromium.googlesource.com/a/native_client/pnacl-llvm.git d0089f0b008e03cfd141f05c80e3b628c2df75c1) nacl-version=0beb554650e57834a9fdda91ca9e3352b330b653
Intel(R) oneAPI DPC++/C++ Compiler 2023.2.0 (2023.2.0.20230721)
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30146 for x86
icc (ICC) 19.0.0.117 20180804
clang version 12.0.1 ([email protected]:ziglang/zig-bootstrap.git 8cc2870e09320a390cafe4e23624e8ed40bd363c)
zapcc clang version 5.0.0 (trunk 295600) (based on LLVM 5.0.0svn) built on Feb 26 2017 19:10:16
nvc++ 23.9-0 64-bit target on x86-64 Linux -tp cascadelake 
mips64-unknown-linux-gnu-g++ (crosstool-NG UNKNOWN) 13.1.0
ecc version 2017-07-16 (http://ellcc.org) based on clang version 5.0.0 (trunk 308147)
clang version 12.0.0
i586-pc-msdosdjgpp-g++ (GCC) 6.4.0

Only a couple of them seemed somewhat unsatisfactory: clang-cl which gave the not-terribly-informative clang version 12.0.0; and PNaCl due to its extreme verbosity. But we could always add a special case for the NaCl compiler.

In case it matters that the command returns 0, for MSVC (but NOT for clang-cl which is both MSVC and CLANG) you should just invoke it with no arguments. (With --version it complains about an unrecognized flag but still prints the version in the first line.)

I guess it would be good to print our target triple (as used for external_deps) somewhere too; this could help fill in the missing information for the uninformative Clang case.

To work around the Zig choking on -mtune problem, you can use our try_c_cxx_flag macro which omits the flag if the compiler errors out when compiling a hello world program.

slipher avatar Dec 04 '23 10:12 slipher

The proposed code parses compiler defines. It only parses things like -v output if the defines are not usable, for example:

  1. zig doesn't set any zig define, we can find some zig word in some version string but that's because it's part of reported repository url used to build the compiler (so a custom build may report another url):
    #define __clang_version__ "16.0.1 (https://github.com/ziglang/zig-bootstrap 710c5d12660235bc4eac103a8c6677c61f0a9ded)"
  2. gcc sets many gcc define but almost all compilers mimic gcc and pretend to be gcc and define all thoes gcc defines…

Otherwise the proposed code relies on macro parsing, it does g++ -E -dM emptyfile.cpp which dumps all the compiler-generated defines to standard out, this works with both gcc and clang derivatives and some compilers mimicking gcc like icc. I didn't went the way of what I did for architecture detection because we need to do more parsing out of defines, but the idea is the same.

Unlike CMake architecture detection which is totally unreliable, CMake compiler detection is actually not bad and considered well tested, since CMake has to write millions of compilation commands. The reason why I want extra detection is that most of the time, CMake only reports the underlying compiler, which is totally good enough for its task.

CMake some extra detection for some clang-based compilers like Intel ICX and armclang but even if it would not detect them and considered them all being clang I guess CMake would be fine, like CMake is already fine with AMD AOCC or Saigo by simply considering it's clang.

But I want to know more, like make the difference between GCC and MinGW, between Clang, PNaCl, Saigo, Wasi, etc. I also wan to know which version of underlying compiler is used (which is useful when a bug comes after a specific clang version for example).

So let's look at the code.

Here is code that gets clang version found by CMake, we will use that as “underlying” version if we detect a clang variant. We should be able to count on it. Well, it would not work if someone publishes a new compiler variant based on a variant, like if someone ports Saigo patches over ICX it would be reported as IntelLLVM and not Clang so this code will not work.

function(detect_custom_clang_version lang file)
	# If CMake already detected the compiler as Clang but we know
	# it's something else.
	if(CUSTOM_${lang}_COMPILER_ID AND CMAKE_${lang}_COMPILER_ID STREQUAL "Clang")
		set(CUSTOM_${lang}_CLANG_VERSION "${CMAKE_${lang}_COMPILER_VERSION}")
		set(CUSTOM_${lang}_CLANG_VERSION "${CUSTOM_${lang}_CLANG_VERSION}" PARENT_SCOPE)
		return()
	endif()

Here is probably what CMake does internally, this is a fallback. This is like doing printf(__clang_version__); but with extra parsing because some compilers like to add some data after the number.

	# Parse “<compiler> -E -dM <source file>”
	execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" ${CUSTOM_${lang}_COMPILER_SUBCOMMAND} -E -dM "${file}"
		OUTPUT_VARIABLE CUSTOM_${lang}_CLANG_OUTPUT
		RESULT_VARIABLE CUSTOM_${lang}_RETURN_CODE
		ERROR_QUIET)

	if (NOT CUSTOM_${lang}_RETURN_CODE) # Success
		if ("${CUSTOM_${lang}_CLANG_OUTPUT}" MATCHES "\#define __clang_version__ ")
			string(REGEX REPLACE ".*#define __clang_version__ \"([^ ]+)[\" ]*.*" "\\1" CUSTOM_${lang}_CLANG_VERSION "${CUSTOM_${lang}_CLANG_OUTPUT}")
			set(CUSTOM_${lang}_CLANG_VERSION "${CUSTOM_${lang}_CLANG_VERSION}" PARENT_SCOPE)
		endif()
	endif()
endfunction()

Here is code that gets GCC version found by CMake, we will use that as “underlying” version if we detect a GCC variant. We should be able to count on it.

function(detect_custom_gcc_version lang file)
	# If CMake already detected the compiler as GCC but we know
	# it's something else.
	if(CUSTOM_${lang}_COMPILER_ID AND CMAKE_${lang}_COMPILER_ID STREQUAL "GNU")
		set(CUSTOM_${lang}_GCC_VERSION "${CMAKE_${lang}_COMPILER_VERSION}")
		set(CUSTOM_${lang}_GCC_VERSION "${CUSTOM_${lang}_GCC_VERSION}" PARENT_SCOPE)
		return()
	endif()

Here is probably what CMake does internally, this is a fallback. First we parse gcc -v output because there is no define known to be unique to GCC, then we do something like printf("%d.%d.%d", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);.

	# Almost all compilers on Earth define __GNUC__, __GNUC_MINOR__, and __GNUC_PATCHLEVEL__,
	# So we first have to check it's really a GCC variant.
	# Parse “<compiler> -v”
	execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" -v
		ERROR_VARIABLE CUSTOM_${lang}_GCC_OUTPUT
		RESULT_VARIABLE CUSTOM_${lang}_RETURN_CODE
		OUTPUT_QUIET)

	if (NOT CUSTOM_${lang}_RETURN_CODE) # Success
		# The existence of this string tells us it's a GCC variant.
		# The version in this string is the same as __VERSION__,
		# the version of the GCC variant, not the version of the upstream
		# GCC we are looking for.
		if ("${CUSTOM_${lang}_GCC_OUTPUT}" MATCHES "\ngcc version ")
			# Parse “<compiler> -E -dM <source file>”
			# No subcommand implemented for now, there may be no usage.
			execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" -E -dM "${file}"
			OUTPUT_VARIABLE CUSTOM_${lang}_GCC_OUTPUT
			RESULT_VARIABLE CUSTOM_${lang}_RETURN_CODE
			ERROR_QUIET)

			if (NOT CUSTOM_${lang}_RETURN_CODE) # Success
				string(REGEX REPLACE ".*#define __GNUC__ ([^ \n]+).*" "\\1" CUSTOM_${lang}_GCC_MAJOR "${CUSTOM_${lang}_GCC_OUTPUT}")
				string(REGEX REPLACE ".*#define __GNUC_MINOR__ ([^ \n]+).*" "\\1" CUSTOM_${lang}_GCC_MINOR "${CUSTOM_${lang}_GCC_OUTPUT}")
				string(REGEX REPLACE ".*#define __GNUC_PATCHLEVEL__ ([^ \n]+).*" "\\1" CUSTOM_${lang}_GCC_PATCHLEVEL "${CUSTOM_${lang}_GCC_OUTPUT}")

				set(CUSTOM_${lang}_GCC_VERSION "${CUSTOM_${lang}_GCC_MAJOR}.${CUSTOM_${lang}_GCC_MINOR}.${CUSTOM_${lang}_GCC_PATCHLEVEL}")
				set(CUSTOM_${lang}_GCC_VERSION "${CUSTOM_${lang}_GCC_VERSION}" PARENT_SCOPE)
				return()
			endif()
		endif()
	endif()
endfunction()

Here is the compiler detection, it requests the compiler to dump defines and parses it. This is similar to what we do for arch detection. It's like doing #ifdef __pnacl__, printf(__VERSION__);, etc.

function(detect_custom_compiler_id_version lang file)
	# Parse “<compiler> -E -dM <source file>”
	execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" -E -dM "${file}"
		OUTPUT_VARIABLE CUSTOM_${lang}_COMPILER_OUTPUT
		RESULT_VARIABLE CUSTOM_${lang}_RETURN_CODE
		ERROR_QUIET)

	if (NOT CUSTOM_${lang}_RETURN_CODE) # Success
		# PNaCL
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES "\#define __pnacl__ 1")
			set(CUSTOM_${lang}_COMPILER_ID "PNaCl" PARENT_SCOPE)

			string(REGEX REPLACE ".*#define __VERSION__ \"([^ ]+).*" "\\1" CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_OUTPUT}")
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			return()
		endif()

		# Saigo
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES "\#define __saigo__ 1")
			set(CUSTOM_${lang}_COMPILER_ID "Saigo" PARENT_SCOPE)

			string(REGEX REPLACE ".*#define __VERSION__ \"Clang ([^ \"]+).*" "\\1" CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_OUTPUT}")
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			return()
		endif()

		# AOCC
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES "CLANG: AOCC")
			set(CUSTOM_${lang}_COMPILER_ID "AOCC" PARENT_SCOPE)

			string(REGEX REPLACE ".*CLANG: AOCC_([^ \"]+).*" "\\1" CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_OUTPUT}")
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			return()
		endif()

		# MinGW64
		# This must be tested before MinGW32 since MinGW64 also defines "__MINGW32__".
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES "\#define __MINGW64__ 1")
			set(CUSTOM_${lang}_COMPILER_ID "MinGW64" PARENT_SCOPE)

			string(REGEX REPLACE ".*#define __VERSION__ \"([^ \"]+).*" "\\1" CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_OUTPUT}")
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			return()
		endif()

		# MinGW32
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES "\#define __MINGW32__ 1")
			set(CUSTOM_${lang}_COMPILER_ID "MinGW32" PARENT_SCOPE)

			string(REGEX REPLACE ".*#define __VERSION__ \"([^ \"]+).*" "\\1" CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_OUTPUT}")
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			return()
		endif()
	endif()

	# Parse “<compiler> --help”
	# There is a bug in cmake: if we set CMAKE_C_COMPILER to "zig:cc",
	# only "zig" is returned when using the ${CMAKE_C_COMPILER} variable,
	# so here the command will be "zig --help".
	execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" --help
		OUTPUT_VARIABLE CUSTOM_${lang}_COMPILER_OUTPUT
		RESULT_VARIABLE CUSTOM_${lang}_RETURN_CODE
		ERROR_QUIET)

	if (NOT CUSTOM_${lang}_RETURN_CODE) # Success
		# Zig
		if ("${CUSTOM_${lang}_COMPILER_OUTPUT}" MATCHES ": zig ")
			set(CUSTOM_${lang}_COMPILER_ID "Zig" PARENT_SCOPE)

			execute_process(COMMAND "${CMAKE_${lang}_COMPILER}" version OUTPUT_VARIABLE CUSTOM_${lang}_COMPILER_VERSION)
			string(STRIP "${CUSTOM_${lang}_COMPILER_VERSION}" CUSTOM_${lang}_COMPILER_VERSION)
			set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)

			# There is a bug in CMake: if we set CMAKE_C_COMPILER to "zig;cc",
			# only "zig" is returned when using the ${CMAKE_C_COMPILER} variable.
			set(C_SUBCOMMAND "cc")
			set(CXX_SUBCOMMAND "c++")

			set(CUSTOM_${lang}_COMPILER_SUBCOMMAND "${${lang}_SUBCOMMAND}")
			set(CUSTOM_${lang}_COMPILER_SUBCOMMAND "${CUSTOM_${lang}_COMPILER_SUBCOMMAND}" PARENT_SCOPE)

			return()
		endif()
	endif()
endfunction()

Then a code running those functions with both C and C++ compilers:

function(detect_custom_compiler lang)
	set(C_EXT ".c")
	set(CXX_EXT ".cpp")

	string(RANDOM RANDOM_SUFFIX)
	set(COMPILER_TEST_FILE "${PROJECT_BINARY_DIR}/test_compiler_${RANDOM_SUFFIX}${${lang}_EXT}")
	file(TOUCH "${COMPILER_TEST_FILE}")

	detect_custom_compiler_id_version("${lang}" "${COMPILER_TEST_FILE}")
	detect_custom_clang_version("${lang}" "${COMPILER_TEST_FILE}")
	detect_custom_gcc_version("${lang}" "${COMPILER_TEST_FILE}")

	file(REMOVE "${COMPILER_TEST_FILE}")

	set(CUSTOM_${lang}_COMPILER_ID "${CUSTOM_${lang}_COMPILER_ID}" PARENT_SCOPE)
	set(CUSTOM_${lang}_COMPILER_VERSION "${CUSTOM_${lang}_COMPILER_VERSION}" PARENT_SCOPE)
	set(CUSTOM_${lang}_COMPILER_SUBCOMMAND "${CUSTOM_${lang}_COMPILER_SUBCOMMAND}" PARENT_SCOPE)

	set(CUSTOM_${lang}_CLANG_VERSION "${CUSTOM_${lang}_CLANG_VERSION}" PARENT_SCOPE)
	set(CUSTOM_${lang}_GCC_VERSION "${CUSTOM_${lang}_GCC_VERSION}" PARENT_SCOPE)
endfunction()

illwieckz avatar Dec 04 '23 16:12 illwieckz

I tried to implement it a bit like I did for architectures:

#define STRING(s) #s
#define XSTRING(s) STRING(s)

#define REPORT(key, value) \
	"##REPORT## ##[## DAEMON_" key " ##|## " value " ##]##"
#define REPORT_VERSION_3(name, major, minor, patch) \
	REPORT(name "_VERSION", XSTRING(major) "." XSTRING(minor) "." XSTRING(patch))
#define REPORT_VERSION_2(name, major, minor) \
	REPORT(name "_VERSION", XSTRING(major) "." XSTRING(minor))
#define REPORT_VERSION_1(name, major) \
	REPORT(name "_VERSION", XSTRING(major))
#define REPORT_VERSION_STRING(name, value) \
	REPORT(name "_VERSION_STRING", value)
#define REPORT_COMPATIBILITY(name) \
	REPORT(name "_COMPATIBILITY", "YES")
#define REPORT_NAME(name) \
	REPORT("COMPILER_NAME", name)

// GCC

#if defined(__GNUC__)
	#pragma message(REPORT_COMPATIBILITY("GCC"))
#endif

#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)
	#pragma message(REPORT_VERSION_3("GCC", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__))
#endif

// Clang

#if defined(__clang__)
	#pragma message(REPORT_COMPATIBILITY("CLANG"))
#endif

#if defined(__clang_major__) && defined(__clang_minor__) && defined(__clang_patchlevel__)
	#pragma message(REPORT_VERSION_3("CLANG", __clang_major__, __clang_minor__, __clang_patchlevel__))
#endif

#if defined(__clang_version__)
	#pragma message(REPORT_VERSION_STRING("CLANG", __clang_version__))
#endif

// ICC

#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE)
	#pragma message(REPORT_VERSION_2("ICC", __INTEL_COMPILER, __INTEL_COMPILER_UPDATE))
#elif defined(_ICC)
	#pragma message(REPORT_VERSION_1("ICC", __ICC))
#endif

// ICX

#if defined(__INTEL_CLANG_COMPILER)
	// This would require extra parsing since it's the form 20240000 for 2024.0.0
	#pragma message(REPORT_VERSION("ICX", __INTEL_CLANG_COMPILER)
#elif defined(__INTEL_LLVM_COMPILER)
	// This would require extra parsing since it's the form 20240000 for 2024.0.0
	#pragma message(REPORT_VERSION("ICX", __INTEL_LLVM_COMPILER)
#endif

// Generic

#if defined(__VERSION__)
	#pragma message(REPORT_VERSION_STRING("COMPILER", __VERSION__))
#endif

// Selection

// There is no Zig specific define.

// There is no AOCC specific define, we should parse other defines:
// #define __VERSION__ "AMD Clang 14.0.6 (CLANG: AOCC_4.0.0-Build#434 2022_10_28)"
// #define __clang_version__ "14.0.6 (CLANG: AOCC_4.0.0-Build#434 2022_10_28)"

#if defined(__INTEL_COMPILER) || defined(__ICC)
	#pragma message(REPORT_NAME("ICC")) // Intel
#elif defined(__INTEL_CLANG_COMPILER) || defined(__INTEL_LLVM_COMPILER)
	#pragma message(REPORT_NAME("ICX")) // IntelLLVM
#elif defined(__wasi__)
	#pragma message(REPORT_NAME("WASI"))
#elif defined(__saigo__)
	#pragma message(REPORT_NAME("Saigo"))
#elif defined(__pnacl__)
	#pragma message(REPORT_NAME("PNaCl"))
#elif defined(__MINGW64__) || defined(__MINGW32__)
	#pragma message(REPORT_NAME("MingGW"))
#elif defined(__clang__)
	#pragma message(REPORT_NAME("Clang"))
#elif defined(__GNUC__)
	#pragma message(REPORT_NAME("GCC")) // GNU
#else
	#pragma message(REPORT_NAME("Unknown"))
#endif

This mostly works:

$ icpc -o /dev/null detect.cpp 2>&1 | grep '##REPORT##' | sed -e 's/.*##REPORT## ##\[## //;s/ ##|## /: /;s/ ##]##.*//'
DAEMON_GCC_COMPATIBILITY: YES
DAEMON_GCC_VERSION: 13.2.0
DAEMON_ICC_VERSION: 2021.10
DAEMON_COMPILER_VERSION_STRING: Intel(R) C++ g++ 13.2 mode
DAEMON_COMPILER_NAME: ICC
$ /opt/intel/oneapi/compiler/latest/bin/icpx -o /dev/null detect.cpp 2>&1 | grep '##REPORT##' | sed -e 's/.*##REPORT## ##\[## //;s/ ##|## /: /;s/ ##]##.*//'
DAEMON_GCC_COMPATIBILITY: YES
DAEMON_GCC_VERSION: 4.2.1
DAEMON_CLANG_COMPATIBILITY: YES
DAEMON_CLANG_VERSION: 17.0.0
DAEMON_CLANG_VERSION_STRING: 17.0.0 (icx 2024.0.0.20231017)
DAEMON_COMPILER_VERSION_STRING: Intel(R) oneAPI DPC++/C++ Compiler 2024.0.0 (2024.0.0.20231017)
DAEMON_COMPILER_NAME: ICX
$ wasi-sdk/bin/clang++ -o /dev/null detect.cpp 2>&1 | grep '##REPORT##' | sed -e 's/.*##REPORT## ##\[## //;s/ ##|## /: /;s/ ##]##.*//'
DAEMON_GCC_COMPATIBILITY: YES
DAEMON_GCC_VERSION: 4.2.1
DAEMON_CLANG_COMPATIBILITY: YES
DAEMON_CLANG_VERSION: 16.0.0
DAEMON_CLANG_VERSION_STRING: 16.0.0 
DAEMON_COMPILER_VERSION_STRING: Clang 16.0.0
DAEMON_COMPILER_NAME: WASI

But for some reasons __pnacl__ and __saigo__ are no set:

$ saigo_newlib/bin/clang++ -o /dev/null detect.cpp 2>&1 | grep '##REPORT##' | sed -e 's/.*##REPORT## ##\[## //;s/ ##|## /: /;s/ ##]##.*//'
DAEMON_GCC_COMPATIBILITY: YES
DAEMON_GCC_VERSION: 4.2.1
DAEMON_CLANG_COMPATIBILITY: YES
DAEMON_CLANG_VERSION: 18.0.0
DAEMON_CLANG_VERSION_STRING: 18.0.0 (https://chromium.googlesource.com/a/native_client/nacl-llvm-project-v10.git 65262d842b48c9888e9e2f7eedd1ee3e2ee1b0a7)
DAEMON_COMPILER_VERSION_STRING: Clang 18.0.0 (https://chromium.googlesource.com/a/native_client/nacl-llvm-project-v10.git 65262d842b48c9888e9e2f7eedd1ee3e2ee1b0a7)
DAEMON_COMPILER_NAME: Clang
$ pnacl/bin/clang++ -o /dev/null detect.cpp 2>&1 | grep '##REPORT##' | sed -e 's/.*##REPORT## ##\[## //;s/ ##|## /: /;s/ ##]##.*//'
DAEMON_GCC_COMPATIBILITY: YES
DAEMON_GCC_VERSION: 4.2.1
DAEMON_CLANG_COMPATIBILITY: YES
DAEMON_CLANG_VERSION: 3.6.0
DAEMON_CLANG_VERSION_STRING: 3.6.0 (https://chromium.googlesource.com/a/native_client/pnacl-clang.git 96b3da27dcefc9d152e51cf54280989b2206d789) (https://chromium.googlesource.com/a/native_client/pnacl-llvm.git d0089f0b008e03cfd141f05c80e3b628c2df75c1)
DAEMON_COMPILER_VERSION_STRING: 4.2.1 Compatible Clang 3.6.0 (https://chromium.googlesource.com/a/native_client/pnacl-clang.git 96b3da27dcefc9d152e51cf54280989b2206d789) (https://chromium.googlesource.com/a/native_client/pnacl-llvm.git d0089f0b008e03cfd141f05c80e3b628c2df75c1)
DAEMON_COMPILER_NAME: Clang

I don't know way, I now fail to get __saigo__, __pnacl__, etc. even with <compiler> -E -dM <emptyfile>. 🤷‍♀️️

illwieckz avatar Dec 04 '23 20:12 illwieckz

Ah no I feel stupid, I need to use the target compilers, like pnacl-clang++ or x86_64-nacl-clang.

illwieckz avatar Dec 04 '23 20:12 illwieckz

But I want to know more, like make the difference between GCC and MinGW, between Clang, PNaCl, Saigo, Wasi, etc.

CMake already knows about MinGW. We have various if (MINGW) in our build. As for NaCl compilers, those can only be used via our custom toolchain file so we should just set needed info in the toolchain file.

I also wan to know which version of underlying compiler is used (which is useful when a bug comes after a specific clang version for example).

Making build-time decisions based on a version could have some utility, but not enough to be worth it. If you just want to know what version something was built with you can save the version string without parsing it.

slipher avatar Dec 04 '23 20:12 slipher

The underlying version is useful, not for build-time decision (I simply concatenate it so it's not usable by CMake), but for bisecting bug that only occurs with specific compiler versions like:

  • https://github.com/DaemonEngine/Daemon/issues/839

In this issue I used such compiler detection to make sure the bug started to appear when a compiler was based on clang > 13.

illwieckz avatar Dec 04 '23 20:12 illwieckz

The primary purpose of such detection is to print compiler versions in daemon.log for when we get bug reports.

illwieckz avatar Dec 04 '23 20:12 illwieckz

One thing that can be used for decision is like “if gcc-based or clang-based (but not pnacl), enable PCH”.

illwieckz avatar Dec 04 '23 21:12 illwieckz

I pushed a different implementation, similar to the one I did to detect architectures (i.e. relying on preprocesor to execute some #ifdef, etc.).

illwieckz avatar Dec 05 '23 05:12 illwieckz

Note that DaemonCompiler.c and DaemonCompiler.cpp are the exact same file, CMake is annoying because it requires the file extension, I'll look for a solution without file duplication later.

illwieckz avatar Dec 05 '23 05:12 illwieckz

I rebased on current master, this looks ready to me.

I find it very useful when tracking down issues like:

  • https://github.com/Unvanquished/Unvanquished/issues/2682
  • https://github.com/DaemonEngine/Daemon/issues/839

This is especially useful when printing the underlying compiler version of a fork (it may not be that easy to get it otherwise).

When building it prints lines like this:

-- Detected C compiler: MSVC 19.29.30154.0 cl.exe
-- Detected C++ compiler: MSVC 19.29.30154.0 cl.exe
-- Detected C compiler: GCC 13.2.0 gcc
-- Detected C++ compiler: GCC 13.2.0 g++
-- Detected C compiler: Clang 16.0.6 clang
-- Detected C++ compiler: Clang 16.0.6 clang++
-- Detected C compiler: AppleClang 10.0.1/clang-10.0.1 clang
-- Detected C++ compiler: AppleClang 10.0.1/clang-10.0.1 clang++
-- Detected C compiler: AOCC 4.2.0/clang-16.0.3 clang
-- Detected C++ compiler: AOCC 4.2.0/clang-16.0.3 clang++
-- Detected C compiler: ICX 2024.1.0/clang-18.0.0 icx
-- Detected C++ compiler: ICX 2024.1.0/clang-18.0.0 icpx
-- Detected C compiler: MinGW 12.0.0/gcc-12.0.0 x86_64-w64-mingw32-gcc
-- Detected C++ compiler: MinGW 12.0.0/gcc-12.0.0 x86_64-w64-mingw32-g++
-- Detected C compiler: PNaCl 4.2.1/clang-3.6.0 pnacl-clang
-- Detected C++ compiler: PNaCl 4.2.1/clang-3.6.0 pnacl-clang++

When running the game it prints lines like this:

Unvanquished 0.54.1 Linux amd64 (GCC 13.2.0 g++) Apr 30 2024
SGame Linux amd64 (GCC 13.2.0 g++) Apr 30 2024
CGame Linux amd64 (GCC 13.2.0 g++) Apr 30 2024
Unvanquished 0.54.1 Linux amd64 (Clang 16.0.6 clang++) Apr 30 2024
SGame Linux amd64 (Clang 16.0.6 clang++) Apr 30 2024
CGame Linux amd64 (Clang 16.0.6 clang++) Apr 30 2024
Unvanquished 0.54.1 Linux amd64 (AOCC 4.2.0/clang-16.0.3 clang++) Apr 30 2024
SGame Linux amd64 (AOCC 4.2.0/clang-16.0.3 clang++) Apr 30 2024
CGame Linux amd64 (AOCC 4.2.0/clang-16.0.3 clang++) Apr 30 2024
Unvanquished 0.54.1 Linux amd64 (ICX 2024.1.0/clang-18.0.0 icpx) Apr 30 2024
SGame Linux amd64 (ICX 2024.1.0/clang-18.0.0 icpx) Apr 30 2024
CGame Linux amd64 (ICX 2024.1.0/clang-18.0.0 icpx) Apr 30 2024
Unvanquished 0.54.1 Windows amd64 (MinGW 12.0.0/gcc-12.0.0 x86_64-w64-mingw32-g++) Apr 30 2024
SGame NaCl nacl (PNaCl 4.2.1/clang-3.6.0 pnacl-clang++) Apr 30 2024
CGame NaCl nacl (PNaCl 4.2.1/clang-3.6.0 pnacl-clang++) Apr 30 2024

illwieckz avatar Apr 30 '24 05:04 illwieckz

I think I still stand by https://github.com/DaemonEngine/Daemon/pull/982#issuecomment-1838248692. If we want to show something about the compiler version, just grab the version string, which

  • Requires much less code to implement
  • Can detect compilers we don't know about

slipher avatar May 01 '24 15:05 slipher

The __VERSION__ string is not reliable. For example AppleClang:

4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.46.4)

The 4.2.1 number at the very beginning of the string is just the __GNUC__ one, the GCC version AppleClang pretends to be. It has nothing to do with the compiler version at all (neither Apple version, neither Clang version), and while LLVM 10.0.1 is the actual LLVM/Clang version, and the number in parentheses clang-1001.0.46.4 only means something internal to Apple, it's not an Clang version.

For example AOCC (AMD compiler):

16.0.3 (CLANG: AOCC_4.2.0-Build#89 2023_12_13)

The 16.0.3 number at the very beginning of the string is the actual LLVM/Clang version this compiler is based on (not the one it claims to be compatible with, and definitely not an emulated GCC version). The actual version string of the compiler is in parentheses. The 16.0.3

For example ICC (historical Intel compiler):

Intel(R) C++ g++ 13.2 mode

The 13.2 number is just the __GNUC__ one, the GCC version ICC pretends to be. The actual version number of the compiler (2021.10) is not written at all.

For example ICX (latest Intel compiler):

18.0.0 (icx 2024.1.0.20240308)

This Clang-based compiler __VERSION__ string format is basically similar to what does AMD with Clang-based AOCC, but not what Apple does with Clang-based AppleClang…

So basically just with 4 compilers we have:

  • __GNUC__ Compatible Apple LLVM ___clang_major__.__clang_minor__.__clang_patchlevel__ (clang-__clang_major____clang_minor____clang_patchlevel__.<custom>)
  • __clang_major__.__clang_minor__.__clang_patchlevel__ (CLANG: AOCC_<custom> <custom>)
  • Intel(R) C++ g++ __GNUC__ mode
  • __clang_major__.__clang_minor__.__clang_patchlevel__ (icx <custom>.<custom>)

All but one are based on Clang, none are based on GCC.

just grab the version string, which requires much less code to implement

This whole code is written because the __VERSION__ string doesn't do the job, at all.

Even AppleClang it actually and intentionally lying to the user, and if ICC ctually discloses it is a lie, it doesn't tell the truth.

can detect compilers we don't know about

  • This code fallbacks on CMake own detection if it fails to detect something, this is actually used for MSVC because unlike some other compiler, CMake will always do a better job than me on that part.
  • This reports the underlying GCC or Clang versions of compilers.

And if you talk about the <compiler> -v output string, while it is less lying than the __VERSION__ string, it also has it's own problems, for example ICC is printing a warning when calling it. You actually have to know the compiler is ICC beforehand to set a flag to disable the warning, and if you don't disable the warning but print the first line anyway, you actually print the warning, not the compiler version. Edit: it doesn't work with MSVC anyway, the -v option is basically some GCC backward compatibility no one is obligated to.

illwieckz avatar May 01 '24 20:05 illwieckz

I've never heard of __VERSION__. Indeed I am talking about requesting the version from the command line. I don't think this has a "lying" problem. I tried ICC, ICX, and MSVC in the comment which I already linked in the previous post.

I didn't see a warning first with ICX but that could be because Godbolt always sets an input file. (Or it could be because the site interleaves stdout and stderr in a possibly random order as there is a warning further down.) Shouldn't warnings be on stderr and --version on stdout though? Then it wouldn't be a problem. MSVC does put its version on stderr rather than stdout but that should probably be a special case anyway.

slipher avatar May 02 '24 03:05 slipher

I'm not against printing a line with the output of <compiler> -v but this would be in addition to this, really.

Maybe I misnamed the PR. This does much more than printing the compiler version.

It makes us aware, not only in reading game log, but also in writing CMake code, if the compiler is GCC-based or Clang-based, it also makes the difference between GCC and MinGW, and other things like that.

This just brings more compiler awareness within CMake than what CMake provides.

This is also why even if we would only print the output of <compiler> -v, I would implement this and keep this.

As an example, I can now do this:

if (DAEMON_CXX_COMPILER_NAME STREQUAL "PNaCl")

Instead of this:

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NACL)

And this code will not have to change in my Saigo branch, because we know the compiler for real.

Without this code, while transitionning to Saigo, we would have to write this line this way:

if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NACL AND NOT USE_NACL_SAIGO)

This makes things much much easier in multiple ways.

Also, the lines printed by this code are actually parseable, even for human it makes it easier to have common patterns to read.

I'm not against printing the ouput of <compiler> -v, it can even bring more informations (compiler build number and build date for some), but this would not replace what is implemented here that is meant to bring as less ambiguous information as possible.

illwieckz avatar May 02 '24 04:05 illwieckz

CMake already knows about GCC, Clang, and MinGW. We use the MINGW variable often.

The toolchain files for NaCl toolchains are written by us ourselves so we can simply write any needed information in there. We can add e.g. NACL_PNACL or NACL_SAIGO variables. Then we can use them like if (NACL_SAIGO) etc. (An undefined variable evaluates to false).

BTW you should be using substring tests like CMAKE_CXX_COMPILER_ID MATCHES "Clang" instead of CMAKE_CXX_COMPILER_ID STREQUAL "Clang" because the compiler ID thing distinguishes several variants of Clang. I think the boolean variable approach is actually better than defining a single compiler type since you can have things like the clang-cl driver which is a Clang, but also MSVC is true.

slipher avatar May 02 '24 05:05 slipher

I don't really trust https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html For example it claims to know about ARMClang, but truth is it does not.

CMake already knows about MinGW. We use the MINGW variable often.

And then what? For printing a compiler string we build the string from that? That would just lead to the third implementation of this PR!

The current implementation in that PR is already the second implementation as the first one was parsing compiler output.

illwieckz avatar May 02 '24 05:05 illwieckz

BTW you should be using substring tests like CMAKE_CXX_COMPILER_ID MATCHES "Clang" instead of CMAKE_CXX_COMPILER_ID STREQUAL "Clang"

Actually CMake names ICX IntelLLVM, not IntelClang. The *Clang match is not reliable.

illwieckz avatar May 02 '24 05:05 illwieckz

CMake already knows about MinGW. We use the MINGW variable often.

And then what? For printing a compiler string we build the string from that? That would just lead to the third implementation of this PR!

No. My suggestion was to use the --version string for making the string to display at runtime. if (MINGW) is used for making build-time decisions and works fine for that.

BTW you should be using substring tests like CMAKE_CXX_COMPILER_ID MATCHES "Clang" instead of CMAKE_CXX_COMPILER_ID STREQUAL "Clang"

Actually CMake names ICX IntelLLVM, not IntelClang. The *Clang match is not reliable.

Someone claimed that the results you get for this depend on the version you put in cmake_minimum_required and will be according to the documentation for that version.

slipher avatar May 02 '24 18:05 slipher