# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Generate Rust bindings for C headers
#
# Bindgen reads C headers and generates compatible Rust declarations of types
# and APIs. This allows us to keep Rust code in sync with C code that it depends
# on.
#
# Input variables:
# MODULE_BINDGEN_ALLOW_FILES
# MODULE_BINDGEN_ALLOW_FUNCTIONS
# MODULE_BINDGEN_ALLOW_TYPES
# MODULE_BINDGEN_ALLOW_VARS
# MODULE_BINDGEN_BLOCK_TYPES
# MODULE_BINDGEN_CTYPES_PREFIX
# MODULE_BINDGEN_FLAGS
# MODULE_BINDGEN_OUTPUT_ENV_VAR
# MODULE_BINDGEN_SRC_HEADER
# MODULE_BINDGEN_OUTPUT_FILE_NAME
# MODULE_BINDGEN_WRAP_STATIC_FNS

ifeq ($(strip $(MODULE_BINDGEN_SRC_HEADER)),)
$(error $(MODULE): MODULE_BINDGEN_SRC_HEADER is required to use bindgen.mk)
endif

BINDGEN := $(CLANG_TOOLS_BINDIR)/bindgen

ifeq ($(strip $(MODULE_BINDGEN_OUTPUT_FILE_NAME)),)
MODULE_BINDGEN_OUTPUT_FILE := $(call TOBUILDDIR,$(patsubst %.h,%.rs,$(MODULE_BINDGEN_SRC_HEADER)))
else
MODULE_BINDGEN_OUTPUT_FILE := $(call TOBUILDDIR,$(dir $(MODULE_BINDGEN_SRC_HEADER))$(MODULE_BINDGEN_OUTPUT_FILE_NAME).rs)
endif

# Trusty rust is all no_std
ifeq ($(MODULE_IS_KERNEL),true)
MODULE_BINDGEN_FLAGS += --use-core --ctypes-prefix 'core::ffi'
else
MODULE_BINDGEN_FLAGS += --use-core --ctypes-prefix 'trusty_types_sys::types'
endif

ifneq ($(strip $(MODULE_BINDGEN_CTYPES_PREFIX)),)
MODULE_BINDGEN_FLAGS += --ctypes-prefix $(MODULE_BINDGEN_CTYPES_PREFIX)
endif

ifeq ($(call TOBOOL,$(MODULE_BINDGEN_WRAP_STATIC_FNS)),true)
BINDGEN_STATIC_FNS_SRC := $(addsuffix .static_fns.c,$(MODULE_BINDGEN_OUTPUT_FILE))
BINDGEN_STATIC_FNS_OBJ := $(addsuffix .o,$(BINDGEN_STATIC_FNS_SRC))
BINDGEN_STATIC_FNS_DEP := $(addsuffix .d,$(BINDGEN_STATIC_FNS_SRC))

MODULE_BINDGEN_FLAGS += \
	--experimental \
	--wrap-static-fns \
	--wrap-static-fns-path=$(BINDGEN_STATIC_FNS_SRC) \

# The re-exported wrappers trigger dead code errors
MODULE_RUSTFLAGS += -A dead-code

ifeq ($(call TOBOOL,$(TRUSTY_USERSPACE)),true)
$(BINDGEN_STATIC_FNS_OBJ): GLOBAL_OPTFLAGS := $(GLOBAL_SHARED_OPTFLAGS) $(GLOBAL_USER_OPTFLAGS) $(GLOBAL_USER_IN_TREE_OPTFLAGS) $(ARCH_OPTFLAGS)
$(BINDGEN_STATIC_FNS_OBJ): GLOBAL_COMPILEFLAGS := $(GLOBAL_SHARED_COMPILEFLAGS) $(GLOBAL_USER_COMPILEFLAGS) $(GLOBAL_USER_IN_TREE_COMPILEFLAGS)
$(BINDGEN_STATIC_FNS_OBJ): GLOBAL_CFLAGS := $(GLOBAL_SHARED_CFLAGS) $(GLOBAL_USER_CFLAGS) $(GLOBAL_USER_IN_TREE_CFLAGS)
$(BINDGEN_STATIC_FNS_OBJ): GLOBAL_INCLUDES := $(addprefix -I,$(GLOBAL_UAPI_INCLUDES) $(GLOBAL_SHARED_INCLUDES) $(GLOBAL_USER_INCLUDES))

# Add some extra user space definitions that are needed for the musl headers
$(BINDGEN_STATIC_FNS_OBJ): GLOBAL_COMPILEFLAGS += -DTRUSTY_USERSPACE=1 -U_ALL_SOURCE -D_XOPEN_SOURCE=700
endif

-include $(BINDGEN_STATIC_FNS_DEP)

# Save the variables read or written by compile.mk
BINDGEN_SAVED_MODULE_INCLUDES := $(MODULE_INCLUDES)
BINDGEN_SAVED_MODULE_OBJS := $(MODULE_OBJS)
BINDGEN_SAVED_MODULE_SRCS := $(MODULE_SRCS)
BINDGEN_SAVED_MODULE_SRCS_FIRST := $(MODULE_SRCS_FIRST)
MODULE_SRCS := $(BINDGEN_STATIC_FNS_SRC)
MODULE_SRCS_FIRST :=

# Explicitly prepend the -I prefix to MODULE_INCLUDES just like make/module.mk
MODULE_INCLUDES := $(addprefix -I,$(MODULE_INCLUDES))
# We add $TRUSTY_TOP to the include directories because the .static_fns.c file
# includes $MODULE_BINDGEN_SRC_HEADER as is (which could be a relative path)
MODULE_INCLUDES += -I$(TRUSTY_TOP)

include make/compile.mk
ifneq ($(realpath $(MODULE_OBJS)),$(realpath $(BINDGEN_STATIC_FNS_OBJ)))
$(error Internal bindgen build error: mismatch between MODULE_OBJS="$(MODULE_OBJS)" and "$(BINDGEN_STATIC_FNS_OBJ)")
endif

MODULE_INCLUDES := $(BINDGEN_SAVED_MODULE_INCLUDES)
MODULE_OBJS := $(BINDGEN_SAVED_MODULE_OBJS)
MODULE_SRCS := $(BINDGEN_SAVED_MODULE_SRCS)
MODULE_SRCS_FIRST := $(BINDGEN_SAVED_MODULE_SRCS_FIRST)

GENERATED += \
	$(BINDGEN_STATIC_FNS_SRC) \
	$(BINDGEN_STATIC_FNS_OBJ) \
	$(BINDGEN_STATIC_FNS_DEP) \

MODULE_EXPORT_EXTRA_OBJECTS += $(BINDGEN_STATIC_FNS_OBJ)
endif

MODULE_BINDGEN_FLAGS += $(addprefix --allowlist-var ,$(MODULE_BINDGEN_ALLOW_VARS))
MODULE_BINDGEN_FLAGS += $(addprefix --allowlist-type ,$(MODULE_BINDGEN_ALLOW_TYPES))
MODULE_BINDGEN_FLAGS += $(addprefix --blocklist-type ,$(MODULE_BINDGEN_BLOCK_TYPES))
MODULE_BINDGEN_FLAGS += $(addprefix --allowlist-function ,$(MODULE_BINDGEN_ALLOW_FUNCTIONS))
MODULE_BINDGEN_FLAGS += $(addprefix --allowlist-file ,$(MODULE_BINDGEN_ALLOW_FILES))
# other sanitizer flags if present will cause bindgen to fail unless we pass
# -fno-sanitize-ignorelist, see https://issues.chromium.org/issues/40265121
BINDGEN_MODULE_COMPILEFLAGS := $(MODULE_COMPILEFLAGS) -fvisibility=default -fno-sanitize-ignorelist

ifeq ($(call TOBOOL,$(TRUSTY_USERSPACE)),true)
# library.mk overrides GLOBAL_COMPILEFLAGS and GLOBAL_INCLUDES for the .o, so we
# must do the same here for bindgen
$(MODULE_BINDGEN_OUTPUT_FILE): GLOBAL_COMPILEFLAGS := $(GLOBAL_SHARED_COMPILEFLAGS) $(GLOBAL_USER_COMPILEFLAGS) $(GLOBAL_USER_IN_TREE_COMPILEFLAGS) $(GLOBAL_USER_IN_TREE_CPPFLAGS)
$(MODULE_BINDGEN_OUTPUT_FILE): GLOBAL_INCLUDES := $(addprefix -I,$(GLOBAL_UAPI_INCLUDES) $(GLOBAL_SHARED_INCLUDES) $(GLOBAL_USER_INCLUDES))
endif

-include $(MODULE_BINDGEN_OUTPUT_FILE).d

MODULE_BINDGEN_DEFINES := $(MODULE_DEFINES)
MODULE_BINDGEN_DEFINES += MODULE_BINDGEN_FLAGS=\"$(call clean_defines,$(MODULE_BINDGEN_FLAGS))\"
MODULE_BINDGEN_DEFINES += BINDGEN_MODULE_COMPILEFLAGS=\"$(call clean_defines,$(BINDGEN_MODULE_COMPILEFLAGS))\"
MODULE_BINDGEN_DEFINES += BINDGEN_MODULE_INCLUDES=\"$(call clean_defines,$(BINDGEN_MODULE_INCLUDES))\"

MODULE_BINDGEN_CONFIG := $(call TOBUILDDIR,$(dir $(MODULE_BINDGEN_SRC_HEADER))/module_bindgen_config.h)
$(MODULE_BINDGEN_CONFIG): MODULE_BINDGEN_DEFINES:=$(MODULE_BINDGEN_DEFINES)
$(MODULE_BINDGEN_CONFIG): MODULE:=$(MODULE)
$(MODULE_BINDGEN_CONFIG): configheader
	@$(call INFO_DONE,$(MODULE),generating bindgen config header, $@)
	@$(call MAKECONFIGHEADER,$@,MODULE_BINDGEN_DEFINES)

$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): BINDGEN := $(BINDGEN)
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): BINDGEN_MODULE_COMPILEFLAGS := $(BINDGEN_MODULE_COMPILEFLAGS)
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): BINDGEN_MODULE_INCLUDES := $(addprefix -I,$(MODULE_INCLUDES))
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): ARCH_COMPILEFLAGS := $(ARCH_$(ARCH)_COMPILEFLAGS)
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): DEFINES := $(addprefix -D,$(MODULE_DEFINES))
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): MODULE_BINDGEN_FLAGS := $(MODULE_BINDGEN_FLAGS)
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): RUSTFMT_PATH := $(RUST_BINDIR)/rustfmt
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC): MODULE_BINDGEN_OUTPUT_FILE := $(MODULE_BINDGEN_OUTPUT_FILE)
$(MODULE_BINDGEN_OUTPUT_FILE) $(BINDGEN_STATIC_FNS_SRC) &: $(MODULE_BINDGEN_SRC_HEADER) $(BINDGEN) $(MODULE_SRCDEPS) $(CONFIGHEADER) $(MODULE_BINDGEN_CONFIG)
	@$(MKDIR)
	$(NOECHO)
	CLANG_PATH=$(BINDGEN_CLANG_PATH) \
	LIBCLANG_PATH=$(BINDGEN_LIBCLANG_PATH) \
	RUSTFMT=$(RUSTFMT_PATH) \
	$(BINDGEN) $< -o $(MODULE_BINDGEN_OUTPUT_FILE) $(MODULE_BINDGEN_FLAGS) --depfile $(MODULE_BINDGEN_OUTPUT_FILE).d -- $(GLOBAL_COMPILEFLAGS) $(ARCH_COMPILEFLAGS) $(BINDGEN_MODULE_COMPILEFLAGS) $(BINDGEN_MODULE_INCLUDES) $(GLOBAL_INCLUDES) $(DEFINES) \
	|| ( \
	    err=$$?; \
	    echo "Bindgen failed with exit code $$err" >&2; \
	    rm -f $@; \
	    exit $$err \
	   )

MODULE_SRCDEPS += $(MODULE_BINDGEN_OUTPUT_FILE)

ifeq ($(MODULE_BINDGEN_OUTPUT_ENV_VAR),)
MODULE_BINDGEN_OUTPUT_ENV_VAR := BINDGEN_INC_FILE
endif

# MODULE_BINDGEN_OUTPUT_ENV_VAR is not compatible with Soong as Soong does not allow
# custom `envflags`. bindgen modules should resort to using `MODULE_BINDGEN_OUTPUT_FILE`
# and `include!(concat!(env!("OUT_DIR"), "/{MODULE_BINDGEN_OUTPUT_FILE}"))` to include
# the generated bindgen file.
MODULE_RUST_ENV := $(MODULE_RUST_ENV) $(MODULE_BINDGEN_OUTPUT_ENV_VAR)=$(MODULE_BINDGEN_OUTPUT_FILE) OUT_DIR=$(dir $(MODULE_BINDGEN_OUTPUT_FILE))

$(info $(MODULE_BINDGEN_OUTPUT_FILE))

MODULE_BINDGEN_ALLOW_FILES :=
MODULE_BINDGEN_ALLOW_FUNCTIONS :=
MODULE_BINDGEN_ALLOW_TYPES :=
MODULE_BINDGEN_ALLOW_VARS :=
MODULE_BINDGEN_BLOCK_TYPES :=
MODULE_BINDGEN_CONFIG :=
MODULE_BINDGEN_CTYPES_PREFIX :=
MODULE_BINDGEN_DEFINES :=
MODULE_BINDGEN_OUTPUT_ENV_VAR :=
MODULE_BINDGEN_OUTPUT_FILE_NAME :=
MODULE_BINDGEN_SRC_HEADER :=
MODULE_BINDGEN_WRAP_STATIC_FNS :=

BINDGEN :=
MODULE_BINDGEN_FLAGS :=
BINDGEN_MODULE_COMPILEFLAGS :=
MODULE_BINDGEN_OUTPUT_FILE :=
BINDGEN_SAVED_MODULE_INCLUDES :=
BINDGEN_SAVED_MODULE_OBJS :=
BINDGEN_SAVED_MODULE_SRCS :=
BINDGEN_SAVED_MODULE_SRCS_FIRST :=
BINDGEN_STATIC_FNS_DEP :=
BINDGEN_STATIC_FNS_OBJ :=
BINDGEN_STATIC_FNS_SRC :=
