1#-------------------------------------------------------------------------------
2# Copyright (c) 2019-2024, Arm Limited and Contributors. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6#-------------------------------------------------------------------------------
7
8#[===[.rst:
9Compiler abstraction for GCC
10----------------------------
11
12.. cmake:variable:: CROSS_COMPILE
13
14	A ';' separated GCC prefix triplets to use when searching for the cross-compiler.
15	(i.e. ``aarch64-none-elf;aarch64-elf``).
16	The variable can be set on the command line with ``-DCROSS_COMPILE=<value>`` or in the
17	environment. If both is specified, command line takes precedence.
18
19.. cmake:variable:: LIBGCC_PATH
20
21	An absolute path to specify the location of the gcc specific library.  The name
22	of the library is libgcc.a.  Note that it must be the full path with library name.
23	The variable can be set on the command line with ``-DLIBGCC_PATH=<value>`` or in the
24	environment. If both is specified, command line takes precedence.
25
26.. cmake:variable:: LIBGCC_INCLUDE_DIRS
27
28	A semicolon separated list of absolute paths to specify the location of gcc specific header
29	files. The variable can be set on the command line with ``-DLIBGCC_INCLUDE_DIRS=<value>``
30	or in the environment. If both is specified, command line takes precedence.
31
32.. cmake:variable:: LIBGCC_LOCATE_CFLAGS
33
34	The compiler options used when searching for the gcc library (libgcc.a).
35	Setting the value is optional.
36	The variable can be set on the command line with ``-DLIBGCC_LOCATE_CFLAGS=<value>`` or
37	in the environment.
38
39#]===]
40
41include_guard(DIRECTORY)
42
43#Generate a list of tool names to look for. Store the result in CMAKE_<lang>_COMPILER.
44function(gcc_find_tool NAME LANG)
45	string(REGEX REPLACE "([^;]+);" "\\1${NAME};\\1${NAME}.exe;" _gcc_names "${CROSS_COMPILE};")
46	find_program(_cross_compile_gcc NAMES ${_gcc_names} REQUIRED)
47	if (NOT _cross_compile_gcc)
48		string(REPLACE ";" " " _msg "${_gcc_names}")
49		message(FATAL_ERROR "Failed to find ${NAME} with the names: ${_msg}")
50	endif()
51	set(CMAKE_${LANG}_COMPILER ${_cross_compile_gcc} CACHE STRING "${LANG} compiler executable.")
52endfunction()
53
54if(CMAKE_CROSSCOMPILING)
55	if(NOT CROSS_COMPILE AND NOT DEFINED ENV{CROSS_COMPILE})
56		message(FATAL_ERROR "'CROSS_COMPILE' is not defined. Set it to the gcc prefix triplet, ie. cmake <..>-DCROSS_COMPILE=aarch64-elf-")
57	endif()
58
59	set(CROSS_COMPILE $ENV{CROSS_COMPILE} CACHE STRING "Prefix of the cross-compiler commands")
60
61	gcc_find_tool(gcc C)
62	gcc_find_tool(g++ CXX)
63
64	#Official solution to disable compiler checks
65	set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
66endif()
67
68#By default when INTERFACE_INCUDES of libraries linked to an exe are treated
69#as system includes. gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf (gcc 8.2.1) will
70#set C linkage o these files, which will result in compilation errors for C++ projects.
71#This setting fixes that.
72set(CMAKE_NO_SYSTEM_FROM_IMPORTED True)
73
74#[===[.rst:
75.. cmake:command:: compiler_preprocess_file
76
77  .. code-block:: cmake
78
79	compiler_preprocess_file(SRC file.c DST file_pp.c)
80	compiler_preprocess_file(SRC file.c DST file_pp.c
81							 DEFINES USE_LIB INCLUDES include/lib)
82
83  Run the preprocessor on a file and save the output to another file. Optionally
84  provide defines and include paths to the preprocessor.
85
86  Inputs:
87
88  ``SRC``
89	Name of the source file to preprocess.
90
91  ``DST``
92	Where to write the preprocessed output.
93
94  ``TARGET`` (optional)
95	Target that the custom command is tied to.
96
97  ``DEFINES`` (multi, optional)
98	Definitions for the preprocessor.
99
100  ``INCLUDES`` (multi, optional)
101	Include paths for the preprocessor.
102
103#]===]
104function(compiler_preprocess_file)
105	set(_OPTIONS_ARGS)
106	set(_ONE_VALUE_ARGS TARGET SRC DST)
107	set(_MULTI_VALUE_ARGS DEFINES INCLUDES)
108	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
109
110	check_args(compiler_preprocess_file SRC DST)
111
112	set(_flags "")
113	if(_MY_PARAMS_DEFINES)
114		list(TRANSFORM _MY_PARAMS_DEFINES PREPEND -D)
115		list(APPEND _flags ${_MY_PARAMS_DEFINES})
116	endif()
117	if(_MY_PARAMS_INCLUDES)
118		list(TRANSFORM _MY_PARAMS_INCLUDES PREPEND -I)
119		list(APPEND _flags ${_MY_PARAMS_INCLUDES})
120	endif()
121
122	if(_MY_PARAMS_TARGET)
123		add_custom_command(
124			TARGET ${_MY_PARAMS_TARGET}
125			POST_BUILD
126			DEPENDS ${_MY_PARAMS_SRC}
127			COMMAND ${CMAKE_C_COMPILER} -E -P -x assembler-with-cpp ${_flags}
128					${_MY_PARAMS_SRC} -o ${_MY_PARAMS_DST}
129		)
130	else()
131		add_custom_command(
132			DEPENDS ${_MY_PARAMS_SRC}
133			OUTPUT ${_MY_PARAMS_DST}
134			COMMAND ${CMAKE_C_COMPILER} -E -P -x assembler-with-cpp ${_flags}
135					${_MY_PARAMS_SRC} -o ${_MY_PARAMS_DST}
136		)
137	endif()
138endfunction()
139
140#[===[.rst:
141.. cmake:command:: compiler_set_linker_script
142
143  .. code-block:: cmake
144
145	compiler_set_linker_script(TARGET foo FILE foo.ld.S)
146	compiler_set_linker_script(TARGET foo FILE foo.ld.S DEF USE_LIB INC include/lib)
147
148  Set linker script for a target. The function adds an LDFLAG using the
149  toolchain specific syntax to the TARGET_linker_script group, which is applied
150  onto the target by the caller function. FILE will be preprocessed, optionally
151  defines and/or includes can be provided using DEF/INC arguments.
152
153  Inputs:
154
155  ``TARGET``
156	Name of the target.
157
158  ``FILE``
159	Linker script file for the target.
160
161  ``DEF`` (multi, optional)
162	Defines for the linker script preprocessor.
163
164  ``INC`` (multi, optional)
165	Include paths for the linker script preprocessor.
166
167#]===]
168function(compiler_set_linker_script)
169	set(_OPTIONS_ARGS)
170	set(_ONE_VALUE_ARGS TARGET FILE)
171	set(_MULTI_VALUE_ARGS DEF INC)
172	cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
173
174	check_args(compiler_set_linker_script TARGET FILE)
175
176	get_filename_component(_src "${_MY_PARAMS_FILE}" ABSOLUTE)
177	get_filename_component(_src_ext "${_MY_PARAMS_FILE}" EXT)
178	set(_dst "${CMAKE_BINARY_DIR}/${_MY_PARAMS_TARGET}.ld")
179
180	if(NOT ("${_src_ext}" STREQUAL ".ld" OR "${_src_ext}" STREQUAL ".ld.S"))
181		message(WARNING "compiler_set_linker_script(): extension mismatch '${_src}'")
182	endif()
183
184	compiler_preprocess_file(
185		SRC ${_src}
186		DST ${_dst}
187		DEFINES ${_MY_PARAMS_DEF} __LINKER__
188		INCLUDES ${_MY_PARAMS_INC}
189	)
190
191	add_custom_target("${_MY_PARAMS_TARGET}_ld" DEPENDS "${_dst}")
192	add_dependencies("${_MY_PARAMS_TARGET}" "${_MY_PARAMS_TARGET}_ld")
193
194	target_link_options(${_MY_PARAMS_TARGET} PRIVATE "-Wl,--script=${_dst}")
195	set_target_properties(${_MY_PARAMS_TARGET} PROPERTIES LINK_DEPENDS "${_dst}")
196endfunction()
197
198#[===[.rst:
199.. cmake:command:: compiler_generate_binary_output
200
201  .. code-block:: cmake
202
203	compiler_generate_binary_output(TARGET <name> RES <var>)
204
205  Generate binary output for the target. The function converts the output
206  executable into bin file using toolchain specific syntax.
207
208  Inputs:
209
210  ``TARGET``
211	Name of the target.
212
213  Outputs:
214
215  ``RES``
216	Full patch to output file.
217
218#]===]
219function(compiler_generate_binary_output)
220	set(options)
221	set(oneValueArgs TARGET NAME RES)
222	set(multiValueArgs)
223	cmake_parse_arguments(MY "${options}" "${oneValueArgs}"
224						"${multiValueArgs}" ${ARGN} )
225	add_custom_command(
226		TARGET ${MY_TARGET} POST_BUILD
227		COMMAND ${CMAKE_OBJCOPY} -O binary
228				$<TARGET_FILE:${MY_TARGET}>
229				$<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME})
230	if (MY_RES)
231		set(${MY_RES} $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME} PARENT_SCOPE)
232	endif()
233
234endfunction()
235
236#[===[.rst:
237.. cmake:command:: compiler_generate_stripped_elf
238
239  .. code-block:: cmake
240
241	compiler_generate_stripped_elf(TARGET foo NAME foo.stripped.elf RES var)
242
243  Strip all symbols that are not needed for relocation processing and return the location
244  of the result.
245
246  Inputs:
247
248  ``TARGET``
249	Name of the target.
250
251  ``NAME``
252	Name of output file
253
254  Outputs:
255
256  ``RES``
257	Name of variable to store the full path of the stripped executable.
258
259#]===]
260
261function(compiler_generate_stripped_elf)
262	set(options)
263	set(oneValueArgs TARGET NAME RES)
264	set(multiValueArgs)
265	cmake_parse_arguments(MY "${options}" "${oneValueArgs}"
266						"${multiValueArgs}" ${ARGN} )
267
268	add_custom_command(
269		TARGET ${MY_TARGET} POST_BUILD
270		COMMAND ${CMAKE_OBJCOPY} --strip-unneeded
271				$<TARGET_FILE:${MY_TARGET}>
272				$<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME})
273	if (MY_RES)
274		set(${MY_RES} $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME} PARENT_SCOPE)
275	endif()
276endfunction()
277
278#[===[.rst:
279.. cmake:command:: gcc_get_lib_location
280
281  .. code-block:: cmake
282
283	gcc_get_lib_location(TARGET foo NAME foo.stripped.elf RES var)
284
285  Query the location of a specific library part of the GCC binary release. Can
286  be used to find built in libraries like libgcc.a when i.w. -nostdlib option
287  is used.
288
289  The function uses the :variable:`LIBGCC_LOCATE_CFLAGS`.
290
291  Inputs:
292
293  ``LIBRARY_NAME``
294	Name of the library to search for.
295
296  Outputs:
297
298  ``RES``
299	Name of variable to store the full path of the library.
300
301#]===]
302function(gcc_get_lib_location)
303	set(options)
304	set(oneValueArgs LIBRARY_NAME RES)
305	set(multiValueArgs)
306	cmake_parse_arguments(MY "${options}" "${oneValueArgs}"
307						"${multiValueArgs}" ${ARGN} )
308
309	if (DEFINED ENV{LIBGCC_LOCATE_CFLAGS})
310		set(LIBGCC_LOCATE_CFLAGS $ENV{LIBGCC_LOCATE_CFLAGS} CACHE STRING "GCC library search options" )
311	endif()
312
313	execute_process(
314		COMMAND ${CMAKE_C_COMPILER} ${LIBGCC_LOCATE_CFLAGS} "--print-file-name=${MY_LIBRARY_NAME}"
315		OUTPUT_VARIABLE _RES
316		RESULT_VARIABLE _GCC_ERROR_CODE
317		OUTPUT_STRIP_TRAILING_WHITESPACE
318		)
319
320	if(_GCC_ERROR_CODE GREATER 0)
321		message(WARNING "GCC (${CMAKE_C_COMPILER}) invocation failed, cannot determine location of library \"${MY_LIBRARY_NAME}\".")
322		set(_RES "${LIBRARY_NAME}-NOTFOUND")
323	endif()
324
325	if (NOT IS_ABSOLUTE "${_RES}")
326		message(WARNING "GCC (${CMAKE_C_COMPILER}) failed to return the location of file \"${MY_LIBRARY_NAME}\".")
327		set(_RES "${LIBRARY_NAME}-NOTFOUND")
328	endif()
329
330	set(${MY_RES} ${_RES} PARENT_SCOPE)
331endfunction()
332
333
334#[===[.rst:
335.. cmake:command:: compiler_set_freestanding
336
337  .. code-block:: cmake
338
339	compiler_set_freestanding(TARGET foo)
340
341  	Configure the target specified for "freestanging" compilation mode. Please see [1] for more information.
342	This will configure the target:
343	  - to block access to the "built in" standard library and its headers
344	  - link compiler specific libraries
345	  - add include paths to compiler specific headers
346
347	All settings will be PUBLIC or INTERFACE (for imported targets) and thus will take effect on targets
348	depending on the configured one.
349
350	The function uses and manipulates the following CACHE variables:
351	  - :variable:`LIBGCC_PATH`
352	  - :variable:`LIBGCC_INCLUDE_DIRS`
353
354	CMake has a spacial behavior which needs a workaround. CMake is automatically filtering out built in compiler
355	include paths from the compiler command line.
356	As a workaround, compiler specific headers are copied to the build directory and set include path to the new
357	location.
358
359	Limitations:
360	  - Inheritance of target settings may be problematic. Compiling components in "freestanding" mode may put
361	    restrictions on reusing these. When such components are installed, the "freestanding" nature shall be
362		propagated to dependencies. This is not tested or implemented.
363
364	1: https://wiki.osdev.org/Implications_of_writing_a_freestanding_C_project
365	2: https://gitlab.kitware.com/cmake/cmake/-/issues/19227
366
367  Inputs:
368
369  ``TARGET``
370	Name of the target to configure.
371
372  Outputs:
373
374  N/A
375
376#]===]
377function(compiler_set_freestanding)
378	set(options)
379	set(oneValueArgs TARGET)
380	set(multiValueArgs)
381	cmake_parse_arguments(MY "${options}" "${oneValueArgs}"
382						"${multiValueArgs}" ${ARGN} )
383
384	# Validate parameters
385	if (NOT DEFINED MY_TARGET)
386		message(FATAL_ERROR "Mandatory parameter TARGET is missing!")
387	endif()
388
389	### Get the location of libgcc.a
390	# Copy values from environment if present. Note: if the value is already in the CACHE, this set will have no effect.
391	if(DEFINED ENV{LIBGCC_PATH})
392		set(LIBGCC_PATH $ENV{LIBGCC_PATH} CACHE PATH "location of libgcc.a")
393	endif()
394	if (NOT DEFINED LIBGCC_PATH)
395		gcc_get_lib_location(LIBRARY_NAME "libgcc.a" RES _TMP_VAR)
396
397		if (NOT _TMP_VAR)
398			message(FATAL_ERROR "Location of libgcc.a can not be determined. Please set LIBGCC_PATH on the command"
399					" line or in the environment.")
400		endif()
401		set(LIBGCC_PATH ${_TMP_VAR} CACHE PATH "location of libgcc.a")
402		unset(_TMP_VAR)
403	endif()
404
405	# Validate LIBGCC_PATH
406	if(NOT EXISTS "${LIBGCC_PATH}" OR IS_DIRECTORY "${LIBGCC_PATH}")
407		message(FATAL_ERROR "LIBGCC_PATH \"${LIBGCC_PATH}\" must be the full path of a library file."
408							" Either set LIBGCC_PATH on the command line (or in the environment), or fix the existing"
409							" value.")
410	endif()
411	message(STATUS "libgcc.a for target \"${MY_TARGET}\" is used from ${LIBGCC_PATH}")
412
413	### Get the location of libgcc specific header files.
414	# Copy values from environment if present. Note: if the value is already in the CACHE, this set will have no effect.
415	if(DEFINED ENV{LIBGCC_INCLUDE_DIRS})
416		set(LIBGCC_INCLUDE_DIRS $ENV{LIBGCC_INCLUDE_DIRS} CACHE STRING "GCC specific include PATHs")
417	endif()
418	if(NOT DEFINED LIBGCC_INCLUDE_DIRS)
419		# We can get the correct path if we ask for a location without a library name
420		gcc_get_lib_location(LIBRARY_NAME "" RES _TMP_VAR)
421
422		if (NOT _TMP_VAR)
423			message(FATAL_ERROR "Location of GCC specific include PATHs can not be determined. Please set"
424					" LIBGCC_INCLUDE_DIRS on the command line or in the environment.")
425		endif()
426
427		set(LIBGCC_INCLUDE_DIRS
428			"${_TMP_VAR}/include"
429			"${_TMP_VAR}/include-fixed" CACHE STRING "GCC specific include PATHs")
430		unset(_TMP_VAR)
431	endif()
432
433	# There is no way to stop cmake from filtering out built in compiler include paths
434	# from compiler command line (see https://gitlab.kitware.com/cmake/cmake/-/issues/19227).
435	# As a workaround copy headers to build directory and set include path to the new
436	# location.
437	# Also validate locations.
438	if (NOT GCC_INCLUDES_MOVED)
439		foreach(_dir IN LISTS LIBGCC_INCLUDE_DIRS)
440			if(NOT IS_DIRECTORY "${_dir}")
441				message(FATAL_ERROR "GCC specific include PATH \"${_dir}\" does not exist.")
442			endif()
443
444			file(COPY "${_dir}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gcc-include")
445			get_filename_component(_TMP_VAR "${_dir}" NAME)
446			list(APPEND _gcc_include_dirs "${CMAKE_CURRENT_BINARY_DIR}/gcc-include/${_TMP_VAR}")
447			message(STATUS "Compiler specific include path \"${_dir}\" mirrored to"
448							"  \"${CMAKE_CURRENT_BINARY_DIR}/gcc-include/${_TMP_VAR}\".")
449		endforeach()
450		unset(_TMP_VAR)
451		set(GCC_INCLUDES_MOVED True CACHE BOOL "GCC include files are already copied.")
452		mark_as_advanced(GCC_INCLUDES_MOVED)
453		# Fix the variable in the CACHE.
454		set(LIBGCC_INCLUDE_DIRS ${_gcc_include_dirs} CACHE STRING "GCC specific include PATHs" FORCE)
455	endif()
456
457	# Configure the target for freestanding mode.
458	target_compile_options(${MY_TARGET} PUBLIC -nostdinc -ffreestanding -fno-builtin)
459	target_include_directories(${MY_TARGET} SYSTEM PUBLIC ${LIBGCC_INCLUDE_DIRS})
460	target_link_options(${MY_TARGET} PUBLIC "-nostdlib" "-nostartfiles")
461	target_link_libraries(${MY_TARGET} PUBLIC "${LIBGCC_PATH}")
462endfunction()
463