|
| 1 | +# Copyright 2026 Arm Limited and/or its affiliates. |
| 2 | +# |
| 3 | +# This source code is licensed under the BSD-style license found in the |
| 4 | +# LICENSE file in the root directory of this source tree. |
| 5 | + |
| 6 | +include(${CMAKE_CURRENT_LIST_DIR}/Utils.cmake) |
| 7 | + |
| 8 | +# Shared setup for Python-based model exporters used by both the configure-time |
| 9 | +# and build-time helper entry points below. |
| 10 | +function( |
| 11 | + _executorch_prepare_python_export |
| 12 | + caller |
| 13 | + script |
| 14 | + output |
| 15 | + working_directory |
| 16 | + python_executable |
| 17 | + out_command_var |
| 18 | + out_output_var |
| 19 | +) |
| 20 | + if(NOT script) |
| 21 | + message(FATAL_ERROR "${caller} requires SCRIPT to be set") |
| 22 | + endif() |
| 23 | + if(NOT output) |
| 24 | + message(FATAL_ERROR "${caller} requires OUTPUT to be set") |
| 25 | + endif() |
| 26 | + if(NOT working_directory) |
| 27 | + message(FATAL_ERROR "${caller} requires WORKING_DIRECTORY to be set") |
| 28 | + endif() |
| 29 | + if(NOT IS_DIRECTORY "${working_directory}") |
| 30 | + message( |
| 31 | + FATAL_ERROR |
| 32 | + "${caller} requires WORKING_DIRECTORY to exist: ${working_directory}" |
| 33 | + ) |
| 34 | + endif() |
| 35 | + |
| 36 | + if(NOT python_executable) |
| 37 | + resolve_python_executable() |
| 38 | + set(python_executable ${PYTHON_EXECUTABLE}) |
| 39 | + endif() |
| 40 | + |
| 41 | + if(NOT EXISTS "${script}") |
| 42 | + message(FATAL_ERROR "Python exporter script does not exist: ${script}") |
| 43 | + endif() |
| 44 | + |
| 45 | + if(IS_ABSOLUTE "${output}") |
| 46 | + set(_et_export_output "${output}") |
| 47 | + else() |
| 48 | + # Resolve relative output paths from the exporter's working directory so |
| 49 | + # configure-time checks and build-time outputs refer to the same file. |
| 50 | + get_filename_component( |
| 51 | + _et_export_output "${output}" ABSOLUTE BASE_DIR "${working_directory}" |
| 52 | + ) |
| 53 | + endif() |
| 54 | + |
| 55 | + get_filename_component(_et_export_output_dir "${_et_export_output}" DIRECTORY) |
| 56 | + if(_et_export_output_dir AND NOT EXISTS "${_et_export_output_dir}") |
| 57 | + file(MAKE_DIRECTORY "${_et_export_output_dir}") |
| 58 | + endif() |
| 59 | + |
| 60 | + set(_et_export_command ${python_executable} ${script} ${ARGN}) |
| 61 | + set(${out_command_var} |
| 62 | + ${_et_export_command} |
| 63 | + PARENT_SCOPE |
| 64 | + ) |
| 65 | + set(${out_output_var} |
| 66 | + "${_et_export_output}" |
| 67 | + PARENT_SCOPE |
| 68 | + ) |
| 69 | +endfunction() |
| 70 | + |
| 71 | +# Run a Python exporter at configure time and require it to produce OUTPUT. |
| 72 | +# |
| 73 | +# Backend-specific lowering options should be expressed in ARGS and remain owned |
| 74 | +# by the Python script. This helper runs the exporter immediately during CMake |
| 75 | +# configure, then verifies that the expected output file was written. |
| 76 | +function(executorch_run_python_exporter out_var) |
| 77 | + cmake_parse_arguments( |
| 78 | + ARG "" "SCRIPT;OUTPUT;WORKING_DIRECTORY;PYTHON_EXECUTABLE" "ARGS" ${ARGN} |
| 79 | + ) |
| 80 | + |
| 81 | + _executorch_prepare_python_export( |
| 82 | + executorch_run_python_exporter |
| 83 | + "${ARG_SCRIPT}" |
| 84 | + "${ARG_OUTPUT}" |
| 85 | + "${ARG_WORKING_DIRECTORY}" |
| 86 | + "${ARG_PYTHON_EXECUTABLE}" |
| 87 | + _et_export_command |
| 88 | + _et_export_output |
| 89 | + ${ARG_ARGS} |
| 90 | + ) |
| 91 | + |
| 92 | + execute_process( |
| 93 | + COMMAND ${_et_export_command} |
| 94 | + WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" |
| 95 | + RESULT_VARIABLE _et_export_status |
| 96 | + OUTPUT_VARIABLE _et_export_stdout |
| 97 | + ERROR_VARIABLE _et_export_stderr |
| 98 | + ) |
| 99 | + |
| 100 | + if(NOT _et_export_status EQUAL 0) |
| 101 | + message( |
| 102 | + FATAL_ERROR |
| 103 | + "Python exporter failed for ${ARG_SCRIPT}\nstdout:\n${_et_export_stdout}\nstderr:\n${_et_export_stderr}" |
| 104 | + ) |
| 105 | + endif() |
| 106 | + |
| 107 | + if(NOT EXISTS "${_et_export_output}") |
| 108 | + message( |
| 109 | + FATAL_ERROR |
| 110 | + "Python exporter ${ARG_SCRIPT} completed but did not produce ${_et_export_output}" |
| 111 | + ) |
| 112 | + endif() |
| 113 | + |
| 114 | + set(${out_var} |
| 115 | + "${_et_export_output}" |
| 116 | + PARENT_SCOPE |
| 117 | + ) |
| 118 | +endfunction() |
| 119 | + |
| 120 | +# Add a build-time target that runs a Python exporter and materializes OUTPUT. |
| 121 | +# Unlike executorch_run_python_exporter(), this helper defers execution until |
| 122 | +# the build graph runs and exposes the generated file through a custom target. |
| 123 | +function(executorch_add_python_export_target) |
| 124 | + cmake_parse_arguments( |
| 125 | + ARG "" "TARGET;SCRIPT;OUTPUT;WORKING_DIRECTORY;PYTHON_EXECUTABLE" |
| 126 | + "ARGS;DEPENDS" ${ARGN} |
| 127 | + ) |
| 128 | + |
| 129 | + if(NOT ARG_TARGET) |
| 130 | + message( |
| 131 | + FATAL_ERROR |
| 132 | + "executorch_add_python_export_target requires TARGET to be set" |
| 133 | + ) |
| 134 | + endif() |
| 135 | + |
| 136 | + _executorch_prepare_python_export( |
| 137 | + executorch_add_python_export_target |
| 138 | + "${ARG_SCRIPT}" |
| 139 | + "${ARG_OUTPUT}" |
| 140 | + "${ARG_WORKING_DIRECTORY}" |
| 141 | + "${ARG_PYTHON_EXECUTABLE}" |
| 142 | + _et_export_command |
| 143 | + _et_export_output |
| 144 | + ${ARG_ARGS} |
| 145 | + ) |
| 146 | + |
| 147 | + add_custom_command( |
| 148 | + OUTPUT ${_et_export_output} |
| 149 | + COMMAND ${_et_export_command} |
| 150 | + COMMAND_EXPAND_LISTS |
| 151 | + DEPENDS ${ARG_SCRIPT} ${ARG_DEPENDS} |
| 152 | + WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" |
| 153 | + COMMENT "Generating model with ${ARG_SCRIPT}" |
| 154 | + VERBATIM |
| 155 | + ) |
| 156 | + |
| 157 | + add_custom_target(${ARG_TARGET} DEPENDS ${_et_export_output}) |
| 158 | +endfunction() |
0 commit comments