#!/usr/bin/env python3
# Copyright (c) 2018-2025 The Bitcoin developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

# Allow to easily build native executable.
# Useful for cross compilation.

if(NOT DEFINED __IS_NATIVE_BUILD)
	# Check if we are in a native build or not.
	set(__IS_NATIVE_BUILD 0 CACHE INTERNAL "Indicate if this is a native build")
endif()

if(__IS_NATIVE_BUILD AND CMAKE_CROSSCOMPILING)
	message(FATAL_ERROR "A native build cannot be cross compiled")
endif()

# It is imperative that NATIVE_BUILD_DIR be in the cache.
set(NATIVE_BUILD_DIR "${CMAKE_BINARY_DIR}/native" CACHE PATH "The path of the native build directory" FORCE)

# Only ninja support depfiles and this is a hard error with other generators
# so we need a nice wrapper to handle this mess.
include(CustomCommandWithDepFile)

function(add_native_executable NAME)
	if(__IS_NATIVE_BUILD)
		add_executable(${NAME} EXCLUDE_FROM_ALL ${ARGN})
		# Multi-configuration generators (VS, Xcode) append a per-configuration
		# subdirectory to the specified directory unless the 
		# `RUNTIME_OUTPUT_DIRECTORY` property is defined using a generator
		# expression.
		# Since we don't care about the build configuration for native
		# executables, we can simply drop this subdirectory.
		# Doing so ensure that the path to the binary can always be retrieved.
		set_target_properties(${NAME} PROPERTIES
			RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<0:>"
		)
	else()
		set(NATIVE_TARGET "${NAME}")
		file(RELATIVE_PATH RELATIVE_PATH "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}")
		if(RELATIVE_PATH)
			string(PREPEND NATIVE_TARGET "${RELATIVE_PATH}/")
		endif()
		if("${CMAKE_GENERATOR}" MATCHES "Ninja")
			set(TARGET "${NATIVE_TARGET}")
		else()
			set(TARGET "${NAME}")
		endif()

		set(NATIVE_BINARY "${NATIVE_BUILD_DIR}/${NATIVE_TARGET}")
		set(NATIVE_LINK "${CMAKE_CURRENT_BINARY_DIR}/native-${NAME}")

		configure_file(
			"${CMAKE_SOURCE_DIR}/cmake/templates/NativeBuildRunner.cmake.in"
			"${CMAKE_CURRENT_BINARY_DIR}/build_native_${NAME}.sh"
		)

		# We create a symlink because cmake craps itself if the imported
		# executable has the same name as the executable itself.
		# https://cmake.org/pipermail/cmake/2019-May/069480.html
		add_custom_command_with_depfile(
			OUTPUT "${NATIVE_LINK}"
			COMMENT "Building native ${NATIVE_TARGET}"
			COMMAND "${CMAKE_CURRENT_BINARY_DIR}/build_native_${NAME}.sh"
			DEPENDS
				native-cmake-build
				"${CMAKE_CURRENT_BINARY_DIR}/build_native_${NAME}.sh"
				${ARGN}
			DEPFILE "${NATIVE_LINK}.d"
			VERBATIM USES_TERMINAL
		)

		add_executable(${NAME} IMPORTED GLOBAL)
		set_target_properties(${NAME} PROPERTIES IMPORTED_LOCATION "${NATIVE_BINARY}")

		# This obviously cannot depend on a file for some mysterious reasons only
		# the cmake gods are aware of, so we need a phony custom target.
		add_custom_target("build-native-${NAME}" DEPENDS "${NATIVE_LINK}")
		add_dependencies(${NAME} "build-native-${NAME}")
	endif()
endfunction(add_native_executable)

function(native_target_include_directories)
	if(__IS_NATIVE_BUILD)
		target_include_directories(${ARGN})
	endif()
endfunction(native_target_include_directories)

function(native_add_cmake_flags)
	set_property(GLOBAL APPEND PROPERTY _NATIVE_BUILD_CMAKE_FLAGS ${ARGN})
endfunction(native_add_cmake_flags)

# Internal machinery
function(_gen_native_cmake_target)
	message(STATUS "Configuring native build in ${NATIVE_BUILD_DIR}")

	get_property(ARGSLIST GLOBAL PROPERTY _NATIVE_BUILD_CMAKE_FLAGS)

	list(SORT ARGSLIST)
	list(REMOVE_DUPLICATES ARGSLIST)
	list(JOIN ARGSLIST " " ARGS)

	file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/config")
	configure_file(
		"${CMAKE_SOURCE_DIR}/cmake/templates/NativeCmakeRunner.cmake.in"
		"${CMAKE_BINARY_DIR}/config/run_native_cmake.sh"
	)
endfunction(_gen_native_cmake_target)

function(_gen_native_cmake_hook VAR ACCESS)
	# When CMAKE_CURRENT_LIST_DIR is set to empty, we execute everything.
	if("${VAR}" STREQUAL "CMAKE_CURRENT_LIST_DIR" AND
	   "${CMAKE_CURRENT_LIST_DIR}" STREQUAL "" AND
	   "${ACCESS}" STREQUAL "MODIFIED_ACCESS")
		_gen_native_cmake_target()
	endif()
endfunction(_gen_native_cmake_hook)

if(NOT __IS_NATIVE_BUILD AND NOT TARGET native-cmake-build)
	# Set a hook to execute when everything is set.
	variable_watch(CMAKE_CURRENT_LIST_DIR _gen_native_cmake_hook)

	add_custom_command_with_depfile(
		OUTPUT "${NATIVE_BUILD_DIR}/CMakeCache.txt"
		COMMENT "Preparing native build..."
		COMMAND "${CMAKE_BINARY_DIR}/config/run_native_cmake.sh"
		DEPENDS "${CMAKE_BINARY_DIR}/config/run_native_cmake.sh"
		WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
		DEPFILE "${NATIVE_BUILD_DIR}/CMakeFiles/CMakeCache.txt.d"
		VERBATIM USES_TERMINAL
	)

	add_custom_target(native-cmake-build DEPENDS "${NATIVE_BUILD_DIR}/CMakeCache.txt")

	# Add the native directory to the list of file to cleanup.
	set_property(DIRECTORY "${CMAKE_SOURCE_DIR}" APPEND PROPERTY ADDITIONAL_CLEAN_FILES "${NATIVE_BUILD_DIR}")
endif()
