# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 OR ISC # Module for generating Rust bindings using bindgen-cli # # Background: Symbol Prefix Mechanism # ------------------------------------ # AWS-LC supports building with a symbol prefix (via BORINGSSL_PREFIX) to avoid # symbol collisions when multiple copies of the library are linked into one binary. # The C build achieves this by generating a boringssl_prefix_symbols.h header that # #define-renames every public symbol (e.g., #define SSL_new AWSLC_SSL_new), then # force-including it during compilation. # # For Rust bindings, we must NOT include that prefix header when invoking bindgen. # Instead, we use bindgen's --prefix-link-name option, which adds the prefix to # #[link_name] attributes (for the linker) while keeping the Rust function names # unprefixed. If we included the prefix header, bindgen would see already-prefixed # names and --prefix-link-name would double-prefix them, producing broken bindings. # # This produces bindings like: # extern "C" { # #[link_name = "AWSLC_SSL_new"] // Prefixed for linker # pub fn SSL_new(...) -> ...; // Unprefixed for Rust API # } # Headers to exclude from Rust bindings generation. # Each exclusion should have a comment explaining why. set(RUST_WRAPPER_EXCLUDED_HEADERS # Template files (not actual headers, used by configure_file) "base.h.in" "opensslv.h.in" # Prefix symbol headers must not be included. See module-level comment above. "boringssl_prefix_symbols.h" "boringssl_prefix_symbols_asm.h" "boringssl_prefix_symbols_nasm.inc" # Platform/architecture-specific internal headers (not part of public API) "arm_arch.h" "asm_base.h" "target.h" # SSL headers (conditionally included via AWS_LC_RUST_INCLUDE_SSL define) "ssl.h" "ssl3.h" "tls1.h" "dtls1.h" ) # Discover all OpenSSL headers for dependency tracking # Uses CONFIGURE_DEPENDS on CMake 3.12+ for automatic reconfiguration function(discover_openssl_headers OUT_VAR OUT_EXPERIMENTAL_VAR) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.12") file(GLOB _all_headers CONFIGURE_DEPENDS "${AWSLC_SOURCE_DIR}/include/openssl/*.h") file(GLOB _experimental_headers CONFIGURE_DEPENDS "${AWSLC_SOURCE_DIR}/include/openssl/experimental/*.h") else() file(GLOB _all_headers "${AWSLC_SOURCE_DIR}/include/openssl/*.h") file(GLOB _experimental_headers "${AWSLC_SOURCE_DIR}/include/openssl/experimental/*.h") endif() set(${OUT_VAR} "${_all_headers}" PARENT_SCOPE) set(${OUT_EXPERIMENTAL_VAR} "${_experimental_headers}" PARENT_SCOPE) endfunction() # Generate rust_wrapper.h from template by discovering headers function(generate_rust_wrapper_header) discover_openssl_headers(_all_headers _experimental_headers) # Build the include list, filtering out excluded headers set(_includes "") foreach(_header ${_all_headers}) get_filename_component(_header_name "${_header}" NAME) list(FIND RUST_WRAPPER_EXCLUDED_HEADERS "${_header_name}" _excluded_idx) if(_excluded_idx EQUAL -1) string(APPEND _includes "#include \n") endif() endforeach() # Build the experimental include list set(_experimental_includes "") foreach(_header ${_experimental_headers}) get_filename_component(_header_name "${_header}" NAME) string(APPEND _experimental_includes "#include \n") endforeach() # Set variables for configure_file substitution set(RUST_WRAPPER_INCLUDES "${_includes}") set(RUST_WRAPPER_EXPERIMENTAL_INCLUDES "${_experimental_includes}") # Generate the header from template configure_file( "${AWSLC_SOURCE_DIR}/cmake/rust_wrapper.h.in" "${AWSLC_BINARY_DIR}/include/rust_wrapper.h" @ONLY ) message(STATUS "Generated rust_wrapper.h with discovered headers") endfunction() # Determine the symbol prefix format based on target platform # # Different platforms have different symbol naming conventions: # - Apple platforms (macOS, iOS): Symbols have a leading underscore (_SSL_new) # - 32-bit Windows: Symbols have a leading underscore (_SSL_new) # - Other platforms (Linux, 64-bit Windows): No leading underscore (SSL_new) # # When we add our prefix, we need to account for this: # - Apple/Win32: _AWSLC_SSL_new (prefix goes after the leading underscore) # - Others: AWSLC_SSL_new # # The --prefix-link-name option prepends the given string to link names, # so we format it appropriately for each platform. function(get_symbol_prefix_format PREFIX OUT_VAR) # Use CMAKE_SYSTEM_NAME for cross-compilation support # (WIN32/APPLE variables reflect the HOST, not the TARGET) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS") set(${OUT_VAR} "_${PREFIX}_" PARENT_SCOPE) elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_SIZEOF_VOID_P EQUAL 4) set(${OUT_VAR} "_${PREFIX}_" PARENT_SCOPE) else() set(${OUT_VAR} "${PREFIX}_" PARENT_SCOPE) endif() endfunction() # Generate Rust bindings using bindgen-cli # # Arguments: # OUTPUT_FILE - Path where the generated .rs file will be written # INCLUDE_DIRS - List of include directories for clang # PREFIX - (Optional) Symbol prefix (e.g., "AWSLC") # INCLUDE_SSL - (Optional) Flag to include SSL headers function(generate_rust_bindings) set(options INCLUDE_SSL) set(oneValueArgs OUTPUT_FILE PREFIX) set(multiValueArgs INCLUDE_DIRS) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Discover headers for dependency tracking discover_openssl_headers(_all_openssl_headers _experimental_headers) # Build include path arguments for clang set(_clang_include_args "") foreach(_dir ${ARG_INCLUDE_DIRS}) list(APPEND _clang_include_args "-I${_dir}") endforeach() # Build bindgen arguments (based on aws-lc-rs/aws-lc-sys configuration) # # Note on regex escaping for --allowlist-file: # - CMake requires \\ to represent a single backslash in strings # - The regex itself needs \\ to match a literal backslash (for Windows paths) # - Combined: \\\\ in CMake -> \\ passed to bindgen -> \\ in regex -> matches one backslash # - The pattern matches paths like: .../openssl/ssl.h or ...\openssl\ssl.h set(_bindgen_args "--allowlist-file" ".*(/|\\\\)openssl((/|\\\\)[^/\\\\]+)+\\.h" "--allowlist-file" ".*(/|\\\\)rust_wrapper\\.h" "--rustified-enum" "point_conversion_form_t" "--default-macro-constant-type" "signed" "--with-derive-default" "--with-derive-partialeq" "--with-derive-eq" "--generate" "functions,types,vars,methods,constructors,destructors" "--rust-target" "${RUST_BINDINGS_TARGET_VERSION}" "--formatter" "rustfmt" ) # Add symbol prefix if specified. See module-level comment for why we use # --prefix-link-name instead of including the prefix symbols header. if(ARG_PREFIX AND NOT ARG_PREFIX STREQUAL "") get_symbol_prefix_format("${ARG_PREFIX}" _sym_prefix) list(APPEND _bindgen_args "--prefix-link-name" "${_sym_prefix}") message(STATUS "Rust bindings will use symbol prefix: ${_sym_prefix}") endif() # Build clang arguments (prefix symbols header is intentionally excluded) set(_clang_args "") # Add SSL support if requested if(ARG_INCLUDE_SSL) list(APPEND _clang_args "-DAWS_LC_RUST_INCLUDE_SSL") message(STATUS "Rust bindings: SSL support enabled (INCLUDE_SSL=ON)") else() message(STATUS "Rust bindings: SSL support disabled (INCLUDE_SSL not set)") endif() # Add include paths (unprefixed headers only) list(APPEND _clang_args ${_clang_include_args}) # Ensure output directory exists get_filename_component(_output_dir "${ARG_OUTPUT_FILE}" DIRECTORY) message(STATUS "Rust bindings clang args: ${_clang_args}") message(STATUS "Rust bindings bindgen args: ${_bindgen_args}") # Create custom command for generating bindings # Dependencies include all OpenSSL headers to ensure regeneration when headers change # # Note: VERBATIM is used to ensure proper argument escaping across platforms, # especially important for the regex patterns on Windows. If debugging command-line # issues, be aware that VERBATIM affects how CMake quotes arguments. add_custom_command( OUTPUT "${ARG_OUTPUT_FILE}" COMMAND ${CMAKE_COMMAND} -E make_directory "${_output_dir}" COMMAND ${BINDGEN_EXECUTABLE} ${_bindgen_args} "${AWSLC_BINARY_DIR}/include/rust_wrapper.h" "--output" "${ARG_OUTPUT_FILE}" "--" ${_clang_args} DEPENDS "${AWSLC_BINARY_DIR}/include/rust_wrapper.h" "${AWSLC_SOURCE_DIR}/cmake/rust_wrapper.h.in" ${_all_openssl_headers} ${_experimental_headers} COMMENT "Generating Rust bindings with bindgen-cli" VERBATIM ) # Create target for bindings generation (not part of ALL; built on-demand or via install) add_custom_target(rust_bindings DEPENDS "${ARG_OUTPUT_FILE}") endfunction()