llext: use CMake shared library support on Xtensa

This change reworks the Xtensa support in llext to use CMake's native
shared library support, instead of manually running "gcc -shared".

This change minimizes the differences in llext handling by defining
appropriate CMake targets for the different architectures.

Signed-off-by: Luca Burelli <l.burelli@arduino.cc>
diff --git a/cmake/compiler/gcc/target_xtensa.cmake b/cmake/compiler/gcc/target_xtensa.cmake
index 1778304..5321204 100644
--- a/cmake/compiler/gcc/target_xtensa.cmake
+++ b/cmake/compiler/gcc/target_xtensa.cmake
@@ -17,5 +17,4 @@
   -fPIC
   -nostdlib
   -nodefaultlibs
-  -shared
 )
diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake
index 1444229..8a3b065 100644
--- a/cmake/modules/extensions.cmake
+++ b/cmake/modules/extensions.cmake
@@ -5099,6 +5099,12 @@
 # loadable extensions (llexts).
 #
 
+# Usage:
+#   add_llext_target(<target_name>
+#                    OUTPUT  <output_file>
+#                    SOURCES <source_file>
+#   )
+#
 # Add a custom target that compiles a single source file to a .llext file.
 #
 # Output and source files must be specified using the OUTPUT and SOURCES
@@ -5112,6 +5118,14 @@
 # The C_FLAGS argument can be used to pass additional compiler flags to the
 # compilation of this particular llext.
 #
+# The following custom properties of <target_name> are defined and can be
+# retrieved using the get_target_property() function:
+#
+# - lib_target  Target name for the source compilation and/or link step.
+# - lib_output  The binary file resulting from compilation and/or
+#               linking steps.
+# - pkg_output  The final .llext file.
+#
 # Example usage:
 #   add_llext_target(hello_world
 #     OUTPUT  ${PROJECT_BINARY_DIR}/hello_world.llext
@@ -5131,10 +5145,8 @@
     message(FATAL_ERROR "add_llext_target: CONFIG_LLEXT must be enabled")
   endif()
 
-  # Output file must be provided
-  if(NOT LLEXT_OUTPUT)
-    message(FATAL_ERROR "add_llext_target: OUTPUT argument must be provided")
-  endif()
+  # Source and output files must be provided
+  zephyr_check_arguments_required_all("add_llext_target" LLEXT OUTPUT SOURCES)
 
   # Source list length must currently be 1
   list(LENGTH LLEXT_SOURCES source_count)
@@ -5142,15 +5154,8 @@
     message(FATAL_ERROR "add_llext_target: only one source file is supported")
   endif()
 
-  set(output_file ${LLEXT_OUTPUT})
+  set(llext_pkg_output ${LLEXT_OUTPUT})
   set(source_file ${LLEXT_SOURCES})
-  get_filename_component(output_name ${output_file} NAME)
-
-  # Add user-visible target and dependency
-  add_custom_target(${target_name}
-    COMMENT "Compiling ${output_name}"
-    DEPENDS ${output_file}
-  )
 
   # Convert the LLEXT_REMOVE_FLAGS list to a regular expression, and use it to
   # filter out these flags from the Zephyr target settings
@@ -5166,62 +5171,86 @@
       "$<FILTER:${zephyr_flags},EXCLUDE,${llext_remove_flags_regexp}>"
   )
 
-  # Compile the source file to an object file using current Zephyr settings
-  # but a different set of flags
-  add_library(${target_name}_lib OBJECT ${source_file})
-  target_compile_definitions(${target_name}_lib PRIVATE
+  # Compile the source file using current Zephyr settings but a different
+  # set of flags.
+  # This is currently arch-specific since the ARM loader for .llext files
+  # expects object file format, while the Xtensa one uses shared libraries.
+  set(llext_lib_target ${target_name}_llext_lib)
+  if(CONFIG_ARM)
+
+    # Create an object library to compile the source file
+    add_library(${llext_lib_target} OBJECT ${source_file})
+    set(llext_lib_output $<TARGET_OBJECTS:${llext_lib_target}>)
+
+  elseif(CONFIG_XTENSA)
+
+    # Create a shared library
+    add_library(${llext_lib_target} SHARED ${source_file})
+    set(llext_lib_output $<TARGET_FILE:${llext_lib_target}>)
+
+    # Add the llext flags to the linking step as well
+    target_link_options(${llext_lib_target} PRIVATE
+      ${LLEXT_APPEND_FLAGS}
+    )
+
+  endif()
+
+  target_compile_definitions(${llext_lib_target} PRIVATE
     $<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_DEFINITIONS>
   )
-  target_compile_options(${target_name}_lib PRIVATE
+  target_compile_options(${llext_lib_target} PRIVATE
     ${zephyr_filtered_flags}
     ${LLEXT_APPEND_FLAGS}
     ${LLEXT_C_FLAGS}
   )
-  target_include_directories(${target_name}_lib PRIVATE
+  target_include_directories(${llext_lib_target} PRIVATE
     $<TARGET_PROPERTY:zephyr_interface,INTERFACE_INCLUDE_DIRECTORIES>
   )
-  target_include_directories(${target_name}_lib SYSTEM PUBLIC
+  target_include_directories(${llext_lib_target} SYSTEM PUBLIC
     $<TARGET_PROPERTY:zephyr_interface,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>
   )
-  add_dependencies(${target_name}_lib
+  add_dependencies(${llext_lib_target}
     zephyr_interface
     zephyr_generated_headers
   )
 
-  # Arch-specific conversion of the object file to an llext
+  # Arch-specific packaging of the built binary file into an .llext file
   if(CONFIG_ARM)
 
-    # No conversion required, simply copy the object file
+    # No packaging required, simply copy the object file
     add_custom_command(
-      OUTPUT ${output_file}
-      COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:${target_name}_lib> ${output_file}
-      DEPENDS ${target_name}_lib $<TARGET_OBJECTS:${target_name}_lib>
+      OUTPUT ${llext_pkg_output}
+      COMMAND ${CMAKE_COMMAND} -E copy ${llext_lib_output} ${llext_pkg_output}
+      DEPENDS ${llext_lib_target} ${llext_lib_output}
     )
 
   elseif(CONFIG_XTENSA)
 
-    # Generate an intermediate file name
-    get_filename_component(output_dir ${output_file} DIRECTORY)
-    get_filename_component(output_name_we ${output_file} NAME_WE)
-    set(pre_output_file ${output_dir}/${output_name_we}.pre.llext)
-
-    # Need to convert the object file to a shared library, then strip some sections
+    # Need to strip the shared library of some sections
     add_custom_command(
-      OUTPUT ${output_file}
-      BYPRODUCTS ${pre_output_file}
-      COMMAND ${CMAKE_C_COMPILER} ${LLEXT_APPEND_FLAGS}
-              -o ${pre_output_file}
-              $<TARGET_OBJECTS:${target_name}_lib>
+      OUTPUT ${llext_pkg_output}
       COMMAND $<TARGET_PROPERTY:bintools,strip_command>
               $<TARGET_PROPERTY:bintools,strip_flag>
               $<TARGET_PROPERTY:bintools,strip_flag_remove_section>.xt.*
-              $<TARGET_PROPERTY:bintools,strip_flag_infile>${pre_output_file}
-              $<TARGET_PROPERTY:bintools,strip_flag_outfile>${output_file}
+              $<TARGET_PROPERTY:bintools,strip_flag_infile>${llext_lib_output}
+              $<TARGET_PROPERTY:bintools,strip_flag_outfile>${llext_pkg_output}
               $<TARGET_PROPERTY:bintools,strip_flag_final>
-      DEPENDS ${target_name}_lib $<TARGET_OBJECTS:${target_name}_lib>
+      DEPENDS ${llext_lib_target} ${llext_lib_output}
     )
 
   else()
     message(FATAL_ERROR "add_llext_target: unsupported architecture")
   endif()
+
+  # Add user-visible target and dependency, and fill in properties
+  get_filename_component(output_name ${llext_pkg_output} NAME)
+  add_custom_target(${target_name}
+    COMMENT "Generating ${output_name}"
+    DEPENDS ${llext_pkg_output}
+  )
+  set_target_properties(${target_name} PROPERTIES
+    lib_target ${llext_lib_target}
+    lib_output ${llext_lib_output}
+    pkg_output ${llext_pkg_output}
+  )
 endfunction()