Newer
Older
mbed-os / tools / cmake / mbed_create_distro.cmake
@Jamie Smith Jamie Smith on 9 Oct 2022 7 KB Don't use whole-archive linking (#71)
# Copyright (c) 2021 ARM Limited. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# This script provides mbed_create_distro(), a function that combines a tree of INTERFACE targets
# into a single library target, copying properties appropriately.

# Append the value of PROPERTY from SOURCE to the value of PROPERTY on DESTINATION
function(copy_append_property PROPERTY SOURCE DESTINATION)
	get_property(PROP_IS_DEFINED TARGET ${SOURCE} PROPERTY ${PROPERTY} SET)
	if(PROP_IS_DEFINED)
		get_property(PROP_VALUE TARGET ${SOURCE} PROPERTY ${PROPERTY})
		set_property(TARGET ${DESTINATION} APPEND PROPERTY ${PROPERTY} "${PROP_VALUE}")
	endif()
endfunction(copy_append_property)

# Create a "distribution" of Mbed OS containing the base Mbed and certain modules.
# This distribution only needs to be compiled once and can be referenced in an arbitrary amount of targets.
function(mbed_create_distro NAME) # ARGN: modules...

	# Use an OBJECT library so that weak symbol resolution works properly
	add_library(${NAME} OBJECT EXCLUDE_FROM_ALL)

	# Workaround to ensure that everything that links to this library receives its objects, including other object libraries
    # from here: https://gitlab.kitware.com/cmake/cmake/-/issues/18090#note_1041608
    target_sources(${NAME} INTERFACE $<TARGET_OBJECTS:${NAME}>)

	# Now copy include dirs, compile defs, and compile options (but NOT interface source files) over
	# to the distribution target so they will be passed into things that link to it.
	# To do this, we need to recursively traverse the tree of dependencies.
	set(REMAINING_MODULES ${ARGN})
	set(COMPLETED_MODULES ${ARGN})
	while(NOT "${REMAINING_MODULES}" STREQUAL "")

		#message("Distro: ${NAME}.  REMAINING_MODULES: ${REMAINING_MODULES}")

		list(GET REMAINING_MODULES 0 CURR_MODULE)
		list(REMOVE_AT REMAINING_MODULES 0)

		if(TARGET "${CURR_MODULE}")

			get_property(CURR_MODULE_TYPE TARGET ${CURR_MODULE} PROPERTY TYPE)

			# Pass up mbed linker script property, which is used by the top level code to select the linker script
			copy_append_property(INTERFACE_MBED_LINKER_SCRIPT ${CURR_MODULE} ${NAME})

			if("${CURR_MODULE_TYPE}" STREQUAL "STATIC_LIBRARY")
				# Don't need to do anything other than linking it
				target_link_libraries(${NAME} PUBLIC ${CURR_MODULE})
			else()
				# Make sure that linking to the distro pulls in the compiled code from CURR_MODULE
				target_link_libraries(${NAME} PRIVATE ${CURR_MODULE})

				copy_append_property(INTERFACE_COMPILE_DEFINITIONS ${CURR_MODULE} ${NAME})
				copy_append_property(INTERFACE_COMPILE_OPTIONS ${CURR_MODULE} ${NAME})
				copy_append_property(INTERFACE_INCLUDE_DIRECTORIES ${CURR_MODULE} ${NAME})
				copy_append_property(INTERFACE_LINK_OPTIONS ${CURR_MODULE} ${NAME})

				# Additionally, interface libraries can export precompiled .o files as sources.
				# We need to copy these to the interface sources of the distro, because adding a .o
				# to the sources of an object library does nothing.
				get_property(CURR_MODULE_INTERFACE_SOURCES TARGET ${CURR_MODULE} PROPERTY INTERFACE_SOURCES)
				foreach(INTERFACE_SOURCE ${CURR_MODULE_INTERFACE_SOURCES})
					if("${INTERFACE_SOURCE}" MATCHES ".o$" OR "${INTERFACE_SOURCE}" MATCHES ".obj$")
						# Object file found
						target_sources(${NAME} INTERFACE ${INTERFACE_SOURCE})
					endif()
				endforeach()

				# CMake currently has a limitation that OBJECT libraries cannot link to other OBJECT libraries
				# via the LINK_LIBRARIES property -- CMake will not link the objects in properly :/.
				# see: https://cmake.org/pipermail/cmake/2019-May/069453.html
				# also: https://gitlab.kitware.com/cmake/cmake/-/issues/18090

				if("${CURR_MODULE_TYPE}" STREQUAL "OBJECT_LIBRARY")
					target_sources(${NAME} INTERFACE $<TARGET_OBJECTS:${CURR_MODULE}>)

					# Check if this object library has any other libraries exported through its INTERFACE_SOURCES.
					# If it does, we need to propagate those too.
					foreach(INTERFACE_SOURCE ${CURR_MODULE_INTERFACE_SOURCES})
						if(INTERFACE_SOURCE MATCHES "\\$<TARGET_OBJECTS:.*>")
							target_sources(${NAME} INTERFACE ${INTERFACE_SOURCE})
						endif()
					endforeach()
				endif()


				list(APPEND COMPLETED_MODULES ${CURR_MODULE})

				# find sub-modules of this module
				get_property(SUBMODULES TARGET ${CURR_MODULE} PROPERTY INTERFACE_LINK_LIBRARIES)
				foreach(SUBMODULE ${SUBMODULES})
					if(NOT "${SUBMODULE}" MATCHES "::@") # remove CMake internal CMAKE_DIRECTORY_ID_SEP markers
						# Remove LINK_ONLY genexes from target_link_libraries(... PRIVATE).  We can ignore things wrapped in these
						# because they will already have been handled by the target_link_libraries earlier on.
						if(NOT "${SUBMODULE}" MATCHES "\\$<LINK_ONLY:.*>")
							if(NOT ${SUBMODULE} IN_LIST COMPLETED_MODULES)
								list(APPEND REMAINING_MODULES ${SUBMODULE})
							endif()
						endif()
					endif()
				endforeach()
			endif()
		else()
			# Module is not a target, it's something else like a linker flag or a generator expression
			# So, just link it to the output.
			target_link_libraries(${NAME} INTERFACE ${CURR_MODULE})
		endif()

	endwhile()

endfunction(mbed_create_distro)

# Traverse a tree of targets and create an interface target containing all of the flags (includes/compile options/
# etc) but none of the source files extracted from those targets.
# This is mainly used as a shim so that we don't need to edit 100s of target CMake files and separate out the targets
# containing flags from the targets containing source files.
function(mbed_extract_flags NAME) # ARGN: modules...
	add_library(${NAME} INTERFACE)

	# Now copy include dirs, compile defs, and compile options (but NOT interface source files) over
	# to the distribution target so they will be passed into things that link to it.
	# To do this, we need to recursively traverse the tree of dependencies.
	set(REMAINING_MODULES ${ARGN})
	set(COMPLETED_MODULES ${ARGN})
	while(NOT "${REMAINING_MODULES}" STREQUAL "")

		#message("Flags: ${NAME}.  REMAINING_MODULES: ${REMAINING_MODULES}")

		list(GET REMAINING_MODULES 0 CURR_MODULE)
		list(REMOVE_AT REMAINING_MODULES 0)

		if(TARGET "${CURR_MODULE}")
			copy_append_property(INTERFACE_COMPILE_DEFINITIONS ${CURR_MODULE} ${NAME})
			copy_append_property(INTERFACE_COMPILE_OPTIONS ${CURR_MODULE} ${NAME})
			copy_append_property(INTERFACE_INCLUDE_DIRECTORIES ${CURR_MODULE} ${NAME})
			copy_append_property(INTERFACE_LINK_OPTIONS ${CURR_MODULE} ${NAME})

			list(APPEND COMPLETED_MODULES ${CURR_MODULE})

			# find sub-modules of this module
			get_property(SUBMODULES TARGET ${CURR_MODULE} PROPERTY INTERFACE_LINK_LIBRARIES)
			foreach(SUBMODULE ${SUBMODULES})
				if(NOT "${SUBMODULE}" MATCHES "::@") # remove CMake internal CMAKE_DIRECTORY_ID_SEP markers
					# Remove LINK_ONLY genexes from target_link_libraries(... PRIVATE).  We can ignore things wrapped in these
					# because they will already have been handled by the target_link_libraries earlier on.
					if(NOT "${SUBMODULE}" MATCHES "\\$<LINK_ONLY:.*>")
						if(NOT ${SUBMODULE} IN_LIST COMPLETED_MODULES)
							list(APPEND REMAINING_MODULES ${SUBMODULE})
						endif()
					endif()
				endif()
			endforeach()
		else()
			# Module is not a target, it's something else like a linker flag or a generator expression
			# So, just link it to the output.
			target_link_libraries(${NAME} INTERFACE ${CURR_MODULE})
		endif()

	endwhile()

endfunction(mbed_extract_flags)