################################################################################
# Cargo package infrastructure
#
# This file implements an infrastructure that eases development of package
# .mk files for Cargo packages. It should be used for all packages that use
# Cargo as their build system.
#
# See the Buildroot documentation for details on the usage of this
# infrastructure
#
# In terms of implementation, this Cargo infrastructure requires the .mk file
# to only specify metadata information about the package: name, version,
# download URL, etc.
#
# We still allow the package .mk file to override what the different steps
# are doing, if needed. For example, if <PKG>_BUILD_CMDS is already defined,
# it is used as the list of commands to perform to build the package,
# instead of the default Cargo behaviour. The package can also define some
# post operation hooks.
#
################################################################################

BR_CARGO_HOME = $(DL_DIR)/br-cargo-home

PKG_COMMON_CARGO_ENV = \
	CARGO_HOME=$(BR_CARGO_HOME)

# __CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS is needed to allow
# passing the -Z target-applies-to-host, which is needed together with
# CARGO_TARGET_APPLIES_TO_HOST to fix build problems when target
# architecture == host architecture.

# __CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS="nightly" is to allow
# using nighly features on stable releases, i.e features that are not
# yet considered stable.
#
# CARGO_UNSTABLE_HOST_CONFIG="true" enables the host specific
# configuration feature
#
# CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST="true" enables the nightly
# configuration option target-applies-to-host value to be set
#
# CARGO_TARGET_APPLIES_TO_HOST="false" is actually setting the value
# for this feature, which we disable, to make sure builds where target
# arch == host arch work correctly
PKG_CARGO_ENV = \
	$(PKG_COMMON_CARGO_ENV) \
	__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS="nightly" \
	CARGO_UNSTABLE_HOST_CONFIG="true" \
	CARGO_UNSTABLE_TARGET_APPLIES_TO_HOST="true" \
	CARGO_TARGET_APPLIES_TO_HOST="false" \
	CARGO_BUILD_TARGET="$(RUSTC_TARGET_NAME)" \
	CARGO_HOST_RUSTFLAGS="$(addprefix -C link-args=,$(HOST_LDFLAGS))" \
	CARGO_TARGET_$(call UPPERCASE,$(RUSTC_TARGET_NAME))_LINKER=$(notdir $(TARGET_CROSS))gcc

# We always set both CARGO_PROFILE_DEV and CARGO_PROFILE_RELEASE
# as we are unable to select a build profile using the environment.
#
# Other cargo profiles generally derive from these two profiles.

# Disable incremental compilation to match release default.
#
# Set codegen-units to release default.
#
# Set split-debuginfo to default off for ELF platforms.
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_INCREMENTAL="false" \
	CARGO_PROFILE_RELEASE_INCREMENTAL="false" \
	CARGO_PROFILE_DEV_CODEGEN_UNITS="16" \
	CARGO_PROFILE_RELEASE_CODEGEN_UNITS="16" \
	CARGO_PROFILE_DEV_SPLIT_DEBUGINFO="off" \
	CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO="off"

# Set the optimization level with the release default as fallback.
ifeq ($(BR2_OPTIMIZE_0),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="0" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="0"
else ifeq ($(BR2_OPTIMIZE_1),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="1" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="1"
else ifeq ($(BR2_OPTIMIZE_2),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="2" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="2"
else ifeq ($(BR2_OPTIMIZE_3),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="3" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="3"
else ifeq ($(BR2_OPTIMIZE_G),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="0" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="0"
else ifeq ($(BR2_OPTIMIZE_S),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="s" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="s"
else ifeq ($(BR2_OPTIMIZE_FAST),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="3" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="3"
else
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_OPT_LEVEL="3" \
	CARGO_PROFILE_RELEASE_OPT_LEVEL="3"
endif

ifeq ($(BR2_ENABLE_LTO),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_LTO="true" \
	CARGO_PROFILE_RELEASE_LTO="true"
else
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_LTO="false" \
	CARGO_PROFILE_RELEASE_LTO="false"
endif


ifeq ($(BR2_ENABLE_DEBUG),y)
ifeq ($(BR2_DEBUG_3),y)
# full debug info
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_DEBUG="2" \
	CARGO_PROFILE_RELEASE_DEBUG="2"
else
# line tables only
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_DEBUG="1" \
	CARGO_PROFILE_RELEASE_DEBUG="1"
endif
else
# no debug info
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_DEBUG="0" \
	CARGO_PROFILE_RELEASE_DEBUG="0"
endif

# Enabling debug-assertions enables the runtime debug_assert! macro.
#
# Enabling overflow-checks enables runtime panic on integer overflow.
ifeq ($(BR2_ENABLE_RUNTIME_DEBUG),y)
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_DEBUG_ASSERTIONS="true" \
	CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS="true" \
	CARGO_PROFILE_DEV_OVERFLOW_CHECKS="true" \
	CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS="true"
else
PKG_CARGO_ENV += \
	CARGO_PROFILE_DEV_DEBUG_ASSERTIONS="false" \
	CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS="false" \
	CARGO_PROFILE_DEV_OVERFLOW_CHECKS="false" \
	CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS="false"
endif

#
# This is a workaround for https://github.com/rust-lang/compiler-builtins/issues/420
# and should be removed when fixed upstream
#
ifeq ($(NORMALIZED_ARCH),arm)
	PKG_CARGO_ENV += \
		CARGO_TARGET_$(call UPPERCASE,$(RUSTC_TARGET_NAME))_RUSTFLAGS="-Clink-arg=-Wl,--allow-multiple-definition"
endif

HOST_PKG_CARGO_ENV = \
	$(PKG_COMMON_CARGO_ENV) \
	RUSTFLAGS="$(addprefix -C link-args=,$(HOST_LDFLAGS))"

################################################################################
# inner-cargo-package -- defines how the configuration, compilation and
# installation of a cargo package should be done, implements a few hooks
# to tune the build process for cargo specifities and calls the generic
# package infrastructure to generate the necessary make targets
#
#  argument 1 is the lowercase package name
#  argument 2 is the uppercase package name, including a HOST_ prefix
#             for host packages
#  argument 3 is the uppercase package name, without the HOST_ prefix
#             for host packages
#  argument 4 is the type (target or host)
################################################################################

define inner-cargo-package

# We need host-rustc to run cargo at download time (for vendoring),
# and at build and install time.
$(2)_DOWNLOAD_DEPENDENCIES += host-rustc
$(2)_DEPENDENCIES += host-rustc

$(2)_DOWNLOAD_POST_PROCESS = cargo
$(2)_DL_ENV += CARGO_HOME=$$(BR_CARGO_HOME)

# If building in a sub directory, use that to find the Cargo.toml
ifneq ($$($(2)_SUBDIR),)
$(2)_DOWNLOAD_POST_PROCESS_OPTS += -m$$($(2)_SUBDIR)/Cargo.toml
endif

# Because we append vendored info, we can't rely on the values being empty
# once we eventually get into the generic-package infra. So, we duplicate
# the heuristics here
ifndef $(2)_LICENSE
 ifdef $(3)_LICENSE
  $(2)_LICENSE = $$($(3)_LICENSE)
 endif
endif

# Due to vendoring, it is pretty likely that not all licenses are
# listed in <pkg>_LICENSE. If the license is unset, it is "unknown"
# so adding unknowns to some unknown is still some other unknown,
# so don't append the blurb in that case.
ifneq ($$($(2)_LICENSE),)
$(2)_LICENSE += , vendored dependencies licenses probably not listed
endif

# Note: in all the steps below, we "cd" into the build directory to
# execute the "cargo" tool instead of passing $(@D)/Cargo.toml as the
# manifest-path. Indeed while the latter seems to work, it in fact
# breaks in subtle ways as the way cargo searches for its
# configuration file is based (among other rules) on the current
# directory. This means that if cargo is started outside of a package
# directory, its configuration file will not be taken into account.
#
# Also, we pass:
#  * --offline to prevent cargo from downloading anything: all
#    dependencies should have been built by the download post
#    process logic
#  * --locked to force cargo to use the Cargo.lock file, which ensures
#    that a fixed set of dependency versions is used

#
# Build step. Only define it if not already defined by the package .mk
# file.
#
ifndef $(2)_BUILD_CMDS
ifeq ($(4),target)
define $(2)_BUILD_CMDS
	cd $$($$(PKG)_SRCDIR) && \
	$$(TARGET_MAKE_ENV) \
		$$(TARGET_CONFIGURE_OPTS) \
		$$(PKG_CARGO_ENV) \
		$$($(2)_CARGO_ENV) \
		cargo build \
			--offline \
			$$(if $$(BR2_ENABLE_DEBUG),,--release) \
			--manifest-path Cargo.toml \
			--locked \
			$$($(2)_CARGO_BUILD_OPTS)
endef
else # ifeq ($(4),target)
define $(2)_BUILD_CMDS
	cd $$($$(PKG)_SRCDIR) && \
	$$(HOST_MAKE_ENV) \
		$$(HOST_CONFIGURE_OPTS) \
		$$(HOST_PKG_CARGO_ENV) \
		$$($(2)_CARGO_ENV) \
		cargo build \
			--offline \
			--release \
			--manifest-path Cargo.toml \
			--locked \
			$$($(2)_CARGO_BUILD_OPTS)
endef
endif # ifeq ($(4),target)
endif # ifndef $(2)_BUILD_CMDS

#
# Target installation step. Only define it if not already defined by
# the package .mk file.
#
ifndef $(2)_INSTALL_TARGET_CMDS
define $(2)_INSTALL_TARGET_CMDS
	cd $$($$(PKG)_SRCDIR) && \
	$$(TARGET_MAKE_ENV) \
		$$(TARGET_CONFIGURE_OPTS) \
		$$(PKG_CARGO_ENV) \
		$$($(2)_CARGO_ENV) \
		cargo install \
			--offline \
			--root $$(TARGET_DIR)/usr/ \
			--bins \
			--path ./ \
			--no-track \
			--force \
			--locked \
			-Z target-applies-to-host \
			$$($(2)_CARGO_INSTALL_OPTS)
endef
endif

ifndef $(2)_INSTALL_CMDS
define $(2)_INSTALL_CMDS
	cd $$($$(PKG)_SRCDIR) && \
	$$(HOST_MAKE_ENV) \
		$$(HOST_CONFIGURE_OPTS) \
		$$(HOST_PKG_CARGO_ENV) \
		$$($(2)_CARGO_ENV) \
		cargo install \
			--offline \
			--root $$(HOST_DIR) \
			--bins \
			--path ./ \
			--no-track \
			--force \
			--locked \
			$$($(2)_CARGO_INSTALL_OPTS)
endef
endif

# Call the generic package infrastructure to generate the necessary
# make targets
$(call inner-generic-package,$(1),$(2),$(3),$(4))

endef

################################################################################
# cargo-package -- the target generator macro for Cargo packages
################################################################################

cargo-package = $(call inner-cargo-package,$(pkgname),$(call UPPERCASE,$(pkgname)),$(call UPPERCASE,$(pkgname)),target)
host-cargo-package = $(call inner-cargo-package,host-$(pkgname),$(call UPPERCASE,host-$(pkgname)),$(call UPPERCASE,$(pkgname)),host)
