Chris Kay | d180873 | 2021-07-19 17:23:28 +0100 | [diff] [blame] | 1 | #[=======================================================================[.rst: |
| 2 | ArmTargetLinkerScript |
| 3 | --------------------- |
| 4 | |
| 5 | .. default-domain:: cmake |
| 6 | |
| 7 | .. command:: arm_target_linker_script |
| 8 | |
| 9 | Set the linker script for a target. |
| 10 | |
| 11 | .. code-block:: cmake |
| 12 | |
| 13 | arm_target_linker_script( |
| 14 | TARGET <target> SCRIPT <script> |
| 15 | [PREPROCESSOR SUBTARGET <subtarget> LANGUAGE <language>]) |
| 16 | |
| 17 | Sets the linker script for the target ``<target>`` to the script ``<script>``, |
| 18 | which is optionally first preprocessed with the preprocessor for the language |
| 19 | given by ``<language>``, which creates the target ``<subtarget>``. |
| 20 | |
| 21 | When preprocessing, the following properties are automatically inherited from |
| 22 | the target ``<target>`` and may also be set on the sub-target ``<subtarget>`` in |
| 23 | order to pass additional information to the preprocessor: |
| 24 | |
| 25 | - :prop_tgt:`COMPILE_OPTIONS <prop_tgt:COMPILE_OPTIONS>` |
| 26 | - :prop_tgt:`COMPILE_DEFINITIONS <prop_tgt:COMPILE_DEFINITIONS>` |
| 27 | - :prop_tgt:`INCLUDE_DIRECTORIES <prop_tgt:INCLUDE_DIRECTORIES>` |
| 28 | |
| 29 | Additionally, the linker script automatically inherits flags from both |
| 30 | :variable:`CMAKE_<LANG>_FLAGS <variable:CMAKE_<LANG>_FLAGS>` and |
| 31 | :variable:`CMAKE_<LANG>_FLAGS_<CONFIG> <variable:CMAKE_<LANG>_FLAGS_<CONFIG>>`. |
| 32 | |
| 33 | .. code-block:: cmake |
| 34 | :caption: Usage example |
| 35 | :linenos: |
| 36 | |
| 37 | add_executable(my-executable "main.c") |
| 38 | |
| 39 | arm_target_linker_script( |
| 40 | TARGET my-executable SCRIPT "linker.ld" |
| 41 | PREPROCESSOR TARGET my-executable-lds LANGUAGE C) |
| 42 | |
| 43 | set_property( |
| 44 | TARGET my-executable-lds APPEND |
| 45 | PROPERTY COMPILE_DEFINITIONS "LINKER=1") |
| 46 | #]=======================================================================] |
| 47 | |
| 48 | include_guard() |
| 49 | |
| 50 | include(ArmAssert) |
| 51 | include(ArmPreprocessSource) |
| 52 | |
| 53 | function(arm_target_linker_script) |
| 54 | set(options "") |
| 55 | set(single-args "TARGET;SCRIPT") |
| 56 | set(multi-args "PREPROCESSOR") |
| 57 | |
| 58 | cmake_parse_arguments(PARSE_ARGV 0 ARG |
| 59 | "${options}" "${single-args}" "${multi-args}") |
| 60 | |
| 61 | arm_assert( |
| 62 | CONDITION DEFINED ARG_TARGET |
| 63 | MESSAGE "No value was given for the `TARGET` argument.") |
| 64 | |
| 65 | arm_assert( |
| 66 | CONDITION DEFINED ARG_SCRIPT |
| 67 | MESSAGE "No value was given for the `SCRIPT` argument.") |
| 68 | |
| 69 | cmake_path(ABSOLUTE_PATH ARG_SCRIPT NORMALIZE |
| 70 | BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") |
| 71 | |
| 72 | if(DEFINED ARG_PREPROCESSOR) |
| 73 | _arm_target_linker_script_preprocess(${ARG_PREPROCESSOR} |
| 74 | TARGET "${ARG_TARGET}" SCRIPT "${ARG_SCRIPT}" |
| 75 | OUTPUT ARG_SCRIPT) |
| 76 | endif() |
| 77 | |
| 78 | get_target_property(language "${ARG_TARGET}" LINKER_LANGUAGE) |
| 79 | |
| 80 | if(CMAKE_${language}_COMPILER_ID STREQUAL "ARMClang") |
| 81 | target_link_options("${ARG_TARGET}" |
| 82 | PUBLIC "LINKER:--scatter" "LINKER:${ARG_SCRIPT}") |
| 83 | else() |
| 84 | target_link_options("${ARG_TARGET}" |
| 85 | PUBLIC "LINKER:-T" "LINKER:${ARG_SCRIPT}") |
| 86 | endif() |
| 87 | endfunction() |
| 88 | |
| 89 | function(_arm_target_linker_script_preprocess) |
| 90 | set(options "") |
| 91 | set(single-args "TARGET;SCRIPT;OUTPUT;SUBTARGET;LANGUAGE") |
| 92 | |
| 93 | cmake_parse_arguments(PARSE_ARGV 0 ARG |
| 94 | "${options}" "${single-args}" "${multi-args}") |
| 95 | |
| 96 | arm_assert( |
| 97 | CONDITION (DEFINED ARG_SUBTARGET) AND |
| 98 | (DEFINED ARG_LANGUAGE) |
| 99 | MESSAGE "The preprocessor `SUBTARGET` and `LANGUAGE` arguments must " |
| 100 | "both be provided when preprocessing.") |
| 101 | |
| 102 | _arm_target_linker_script_preprocess_path( |
| 103 | TARGET "${ARG_SUBTARGET}" SCRIPT "${ARG_SCRIPT}" |
| 104 | OUTPUT path) |
| 105 | |
| 106 | arm_preprocess_source( |
| 107 | TARGET "${ARG_SUBTARGET}" LANGUAGE "${ARG_LANGUAGE}" |
| 108 | SOURCE "${ARG_SCRIPT}" OUTPUT "${path}" |
| 109 | INHIBIT_LINEMARKERS) |
| 110 | |
| 111 | set(compile-options "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_OPTIONS>") |
| 112 | set(compile-definitions "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_DEFINITIONS>") |
| 113 | set(include-directories "$<TARGET_PROPERTY:${ARG_TARGET},INCLUDE_DIRECTORIES>") |
| 114 | |
| 115 | foreach(config IN LISTS CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES) |
| 116 | string(TOUPPER "${config}" config) |
| 117 | |
| 118 | separate_arguments(config-compile-options |
| 119 | NATIVE_COMMAND "${CMAKE_${preprocessor-language}_FLAGS_${config}}") |
| 120 | list(PREPEND compile-options |
| 121 | "$<$<CONFIG:${config}>:${config-compile-options}>") |
| 122 | endforeach() |
| 123 | |
| 124 | separate_arguments(global-compile-options |
| 125 | NATIVE_COMMAND "${CMAKE_${preprocessor-language}_FLAGS}") |
| 126 | list(PREPEND compile-options "${global-compile-options}") |
| 127 | |
| 128 | set_target_properties("${ARG_SUBTARGET}" |
| 129 | PROPERTIES COMPILE_OPTIONS "${compile-options}" |
| 130 | COMPILE_DEFINITIONS "${compile-definitions}" |
| 131 | INCLUDE_DIRECTORIES "${include-directories}") |
| 132 | |
| 133 | add_dependencies(${ARG_TARGET} "${ARG_SUBTARGET}") |
| 134 | |
| 135 | set(${ARG_OUTPUT} "${path}" PARENT_SCOPE) |
| 136 | endfunction() |
| 137 | |
| 138 | function(_arm_target_linker_script_preprocess_path result target script) |
| 139 | set(options "") |
| 140 | set(single-args "OUTPUT;TARGET;SCRIPT") |
| 141 | set(multi-args "") |
| 142 | |
| 143 | cmake_parse_arguments(PARSE_ARGV 0 ARG |
| 144 | "${options}" "${single-args}" "${multi-args}") |
| 145 | |
| 146 | # |
| 147 | # Figure out where we're going to place our preprocessed file. This depends |
| 148 | # on whether we're using a multi-config generator or not: |
| 149 | # |
| 150 | # - Single-config: CMakeFiles/${subtarget}.dir |
| 151 | # - Multi-config: CMakeFiles/${subtarget}.dir/$<CONFIG> |
| 152 | # |
| 153 | |
| 154 | get_property(multi-config GLOBAL |
| 155 | PROPERTY GENERATOR_IS_MULTI_CONFIG) |
| 156 | |
| 157 | set(path "${CMAKE_CURRENT_BINARY_DIR}") |
| 158 | |
| 159 | cmake_path(APPEND_STRING path "${CMAKE_FILES_DIRECTORY}") |
| 160 | cmake_path(APPEND path "${ARG_TARGET}.dir") |
| 161 | |
| 162 | if(multi-config) |
| 163 | cmake_path(APPEND path "$<CONFIG>") |
| 164 | endif() |
| 165 | |
| 166 | # |
| 167 | # Try to mirror the behaviour of CMake when deciding the relativized path |
| 168 | # for the preprocessed file. If the source file is a child of the current |
| 169 | # source directory we use its path relative to that, but otherwise we take |
| 170 | # its relative path part. As an example: |
| 171 | # |
| 172 | # - ${CMAKE_CURRENT_SOURCE_DIR}/foo/bar.c -> foo/bar.c.i |
| 173 | # - C:/foo/bar.c -> foo/bar.c.i |
| 174 | # |
| 175 | |
| 176 | cmake_path(IS_PREFIX CMAKE_CURRENT_SOURCE_DIR "${ARG_SCRIPT}" is-child) |
| 177 | |
| 178 | if(is-child) |
| 179 | cmake_path(RELATIVE_PATH ARG_SCRIPT OUTPUT_VARIABLE relative-script) |
| 180 | else() |
| 181 | cmake_path(GET ARG_SCRIPT RELATIVE_PART relative-script) |
| 182 | endif() |
| 183 | |
| 184 | cmake_path(APPEND path "${relative-script}.i") |
| 185 | |
| 186 | set(${ARG_OUTPUT} "${path}" PARENT_SCOPE) |
| 187 | endfunction() |