xref: /llvm-project/llvm/cmake/modules/LLVMDistributionSupport.cmake (revision 4ecbfacf9ecdc5bd9bf699d400c5058071b9500c)
1# Utility functions for packaging an LLVM distribution. See the
2# BuildingADistribution documentation for more details.
3
4# These functions assume a number of conventions that are common across all LLVM
5# subprojects:
6# - The generated CMake exports file for ${project} is called ${project}Targets
7#   (except for LLVM where it's called ${project}Exports for legacy reasons).
8# - The build target for the CMake exports is called ${project}-cmake-exports
9#   (except LLVM where it's just cmake-exports).
10# - The ${PROJECT}${distribution}_HAS_EXPORTS global property holds whether a
11#   project has any exports for a particular ${distribution} (where ${PROJECT}
12#   is the project name in uppercase).
13# - The ${PROJECT}_CMAKE_DIR variable is computed by ${project}Config.cmake to
14#   hold the path of the installed CMake modules directory.
15# - The ${PROJECT}_INSTALL_PACKAGE_DIR variable contains the install destination
16#   for the project's CMake modules.
17
18include_guard(GLOBAL)
19
20if(LLVM_DISTRIBUTION_COMPONENTS AND LLVM_DISTRIBUTIONS)
21  message(FATAL_ERROR "LLVM_DISTRIBUTION_COMPONENTS and LLVM_DISTRIBUTIONS cannot be specified together")
22endif()
23
24if(LLVM_DISTRIBUTION_COMPONENTS OR LLVM_DISTRIBUTIONS)
25  if(LLVM_ENABLE_IDE)
26    message(FATAL_ERROR "LLVM_DISTRIBUTION_COMPONENTS cannot be specified with multi-configuration generators (i.e. Xcode or Visual Studio)")
27  endif()
28endif()
29
30# Build the map of targets to distributions that's used to look up the
31# distribution for a target later. The distribution for ${target} is stored in
32# the global property LLVM_DISTRIBUTION_FOR_${target}.
33function(llvm_distribution_build_target_map)
34  foreach(target ${LLVM_DISTRIBUTION_COMPONENTS})
35    # CMake doesn't easily distinguish between properties that are unset and
36    # properties that are empty (you have to do a second get_property call with
37    # the SET option, which is unergonomic), so just use a special marker to
38    # denote the default (unnamed) distribution.
39    set_property(GLOBAL PROPERTY LLVM_DISTRIBUTION_FOR_${target} "<DEFAULT>")
40  endforeach()
41
42  foreach(distribution ${LLVM_DISTRIBUTIONS})
43    foreach(target ${LLVM_${distribution}_DISTRIBUTION_COMPONENTS})
44      # By default, we allow a target to be in multiple distributions, and use
45      # the last one to determine its export set. We disallow this in strict
46      # mode, emitting a single error at the end for readability.
47      if(LLVM_STRICT_DISTRIBUTIONS)
48        get_property(current_distribution GLOBAL PROPERTY LLVM_DISTRIBUTION_FOR_${target})
49        if(current_distribution AND NOT current_distribution STREQUAL distribution)
50          set_property(GLOBAL APPEND_STRING PROPERTY LLVM_DISTRIBUTION_ERRORS
51            "Target ${target} cannot be in multiple distributions \
52             ${distribution} and ${current_distribution}\n"
53            )
54        endif()
55      endif()
56      set_property(GLOBAL PROPERTY LLVM_DISTRIBUTION_FOR_${target} ${distribution})
57    endforeach()
58  endforeach()
59endfunction()
60
61# The include guard ensures this will only be called once. The rest of this file
62# only defines other functions (i.e. it doesn't execute any more code directly).
63llvm_distribution_build_target_map()
64
65# Look up the distribution a particular target belongs to.
66# - target: The target to look up.
67# - in_distribution_var: The variable with this name is set in the caller's
68#   scope to indicate if the target is in any distribution. If no distributions
69#   have been configured, this will always be set to true.
70# - distribution_var: The variable with this name is set in the caller's scope
71#   to indicate the distribution name for the target. If the target belongs to
72#   the default (unnamed) distribution, or if no distributions have been
73#   configured, it's set to the empty string.
74# - UMBRELLA: The (optional) umbrella target that the target is a part of. For
75#   example, all LLVM libraries have the umbrella target llvm-libraries.
76function(get_llvm_distribution target in_distribution_var distribution_var)
77  if(NOT LLVM_DISTRIBUTION_COMPONENTS AND NOT LLVM_DISTRIBUTIONS)
78    set(${in_distribution_var} YES PARENT_SCOPE)
79    set(${distribution_var} "" PARENT_SCOPE)
80    return()
81  endif()
82
83  cmake_parse_arguments(ARG "" UMBRELLA "" ${ARGN})
84  get_property(distribution GLOBAL PROPERTY LLVM_DISTRIBUTION_FOR_${target})
85  if(ARG_UMBRELLA)
86    get_property(umbrella_distribution GLOBAL PROPERTY LLVM_DISTRIBUTION_FOR_${ARG_UMBRELLA})
87    if(LLVM_STRICT_DISTRIBUTIONS AND distribution AND umbrella_distribution AND
88        NOT distribution STREQUAL umbrella_distribution)
89      set_property(GLOBAL APPEND_STRING PROPERTY LLVM_DISTRIBUTION_ERRORS
90        "Target ${target} has different distribution ${distribution} from its \
91         umbrella target's (${ARG_UMBRELLA}) distribution ${umbrella_distribution}\n"
92        )
93    endif()
94    if(NOT distribution)
95      set(distribution ${umbrella_distribution})
96    endif()
97  endif()
98
99  if(distribution)
100    set(${in_distribution_var} YES PARENT_SCOPE)
101    if(distribution STREQUAL "<DEFAULT>")
102      set(distribution "")
103    endif()
104    set(${distribution_var} "${distribution}" PARENT_SCOPE)
105  else()
106    set(${in_distribution_var} NO PARENT_SCOPE)
107  endif()
108endfunction()
109
110# Get the EXPORT argument to use for an install command for a target in a
111# project. As explained at the top of the file, the project export set for a
112# distribution is named ${project}{distribution}Targets (except for LLVM where
113# it's named ${project}{distribution}Exports for legacy reasons). Also set the
114# ${PROJECT}_${DISTRIBUTION}_HAS_EXPORTS global property to mark the project as
115# having exports for the distribution.
116# - target: The target to get the EXPORT argument for.
117# - project: The project to produce the argument for. IMPORTANT: The casing of
118#   this argument should match the casing used by the project's Config.cmake
119#   file. The correct casing for the LLVM projects is Clang, Flang, LLD, LLVM,
120#   and MLIR.
121# - export_arg_var The variable with this name is set in the caller's scope to
122#   the EXPORT argument for the target for the project.
123# - UMBRELLA: The (optional) umbrella target that the target is a part of. For
124#   example, all LLVM libraries have the umbrella target llvm-libraries.
125function(get_target_export_arg target project export_arg_var)
126  string(TOUPPER "${project}" project_upper)
127  if(project STREQUAL "LLVM")
128    set(suffix "Exports") # legacy
129  else()
130    set(suffix "Targets")
131  endif()
132
133  get_llvm_distribution(${target} in_distribution distribution ${ARGN})
134
135  if(in_distribution)
136    set(${export_arg_var} EXPORT ${project}${distribution}${suffix} PARENT_SCOPE)
137    if(distribution)
138      string(TOUPPER "${distribution}" distribution_upper)
139      set_property(GLOBAL PROPERTY ${project_upper}_${distribution_upper}_HAS_EXPORTS True)
140    else()
141      set_property(GLOBAL PROPERTY ${project_upper}_HAS_EXPORTS True)
142    endif()
143  else()
144    set(${export_arg_var} "" PARENT_SCOPE)
145  endif()
146endfunction()
147
148# Produce a string of CMake include() commands to include the exported targets
149# files for all distributions. See the comment at the top of this file for
150# various assumptions made.
151# - project: The project to produce the commands for. IMPORTANT: See the comment
152#   for get_target_export_arg above for the correct casing of this argument.
153# - includes_var: The variable with this name is set in the caller's scope to
154#   the string of include commands.
155function(get_config_exports_includes project includes_var)
156  string(TOUPPER "${project}" project_upper)
157  set(prefix "\${${project_upper}_CMAKE_DIR}/${project}")
158  if(project STREQUAL "LLVM")
159    set(suffix "Exports.cmake") # legacy
160  else()
161    set(suffix "Targets.cmake")
162  endif()
163
164  if(NOT LLVM_DISTRIBUTIONS)
165    set(${includes_var} "include(\"${prefix}${suffix}\")" PARENT_SCOPE)
166  else()
167    set(includes)
168    foreach(distribution ${LLVM_DISTRIBUTIONS})
169      list(APPEND includes "include(\"${prefix}${distribution}${suffix}\" OPTIONAL)")
170    endforeach()
171    string(REPLACE ";" "\n" includes "${includes}")
172    set(${includes_var} "${includes}" PARENT_SCOPE)
173  endif()
174endfunction()
175
176# Create the install commands and targets for the distributions' CMake exports.
177# The target to install ${distribution} for a project is called
178# ${project}-${distribution}-cmake-exports, where ${project} is the project name
179# in lowercase and ${distribution} is the distribution name in lowercase, except
180# for LLVM, where the target is just called ${distribution}-cmake-exports. See
181# the comment at the top of this file for various assumptions made.
182# - project: The project. IMPORTANT: See the comment for get_target_export_arg
183#   above for the correct casing of this argument.
184function(install_distribution_exports project)
185  string(TOUPPER "${project}" project_upper)
186  string(TOLOWER "${project}" project_lower)
187  if(project STREQUAL "LLVM")
188    set(prefix "")
189    set(suffix "Exports") # legacy
190  else()
191    set(prefix "${project_lower}-")
192    set(suffix "Targets")
193  endif()
194  set(destination "${${project_upper}_INSTALL_PACKAGE_DIR}")
195
196  if(NOT LLVM_DISTRIBUTIONS)
197    get_property(has_exports GLOBAL PROPERTY ${project_upper}_HAS_EXPORTS)
198    if(has_exports)
199      install(EXPORT ${project}${suffix} DESTINATION "${destination}"
200              COMPONENT ${prefix}cmake-exports)
201    endif()
202  else()
203    foreach(distribution ${LLVM_DISTRIBUTIONS})
204      string(TOUPPER "${distribution}" distribution_upper)
205      get_property(has_exports GLOBAL PROPERTY ${project_upper}_${distribution_upper}_HAS_EXPORTS)
206      if(has_exports)
207        string(TOLOWER "${distribution}" distribution_lower)
208        set(target ${prefix}${distribution_lower}-cmake-exports)
209        install(EXPORT ${project}${distribution}${suffix} DESTINATION "${destination}"
210                COMPONENT ${target})
211        if(NOT LLVM_ENABLE_IDE)
212          add_custom_target(${target})
213          get_subproject_title(subproject_title)
214          set_target_properties(${target} PROPERTIES FOLDER "${subproject_title}/Distribution")
215          add_llvm_install_targets(install-${target} COMPONENT ${target})
216        endif()
217      endif()
218    endforeach()
219  endif()
220endfunction()
221
222# Create the targets for installing the configured distributions. The
223# ${distribution} target builds the distribution, install-${distribution}
224# installs it, and install-${distribution}-stripped installs a stripped version,
225# where ${distribution} is the distribution name in lowercase, or "distribution"
226# for the default distribution.
227function(llvm_distribution_add_targets)
228  # This function is called towards the end of LLVM's CMakeLists.txt, so all
229  # errors will have been seen by now.
230  if(LLVM_STRICT_DISTRIBUTIONS)
231    get_property(errors GLOBAL PROPERTY LLVM_DISTRIBUTION_ERRORS)
232    if(errors)
233      string(PREPEND errors
234        "Strict distribution errors (turn off LLVM_STRICT_DISTRIBUTIONS to bypass):\n"
235        )
236      message(FATAL_ERROR "${errors}")
237    endif()
238  endif()
239
240  set(distributions "${LLVM_DISTRIBUTIONS}")
241  if(NOT distributions)
242    # CMake seemingly doesn't distinguish between an empty list and a list
243    # containing one element which is the empty string, so just use a special
244    # marker to denote the default (unnamed) distribution and fix it in the
245    # loop.
246    set(distributions "<DEFAULT>")
247  endif()
248
249  get_property(LLVM_DRIVER_TOOL_SYMLINKS GLOBAL PROPERTY LLVM_DRIVER_TOOL_SYMLINKS)
250
251  foreach(distribution ${distributions})
252    if(distribution STREQUAL "<DEFAULT>")
253      set(distribution_target distribution)
254      # Preserve legacy behavior for LLVM_DISTRIBUTION_COMPONENTS.
255      set(distribution_components ${LLVM_DISTRIBUTION_COMPONENTS} ${LLVM_RUNTIME_DISTRIBUTION_COMPONENTS})
256    else()
257      string(TOLOWER "${distribution}" distribution_lower)
258      set(distribution_target ${distribution_lower}-distribution)
259      set(distribution_components ${LLVM_${distribution}_DISTRIBUTION_COMPONENTS})
260    endif()
261
262    add_custom_target(${distribution_target})
263    add_custom_target(install-${distribution_target})
264    add_custom_target(install-${distribution_target}-stripped)
265    get_subproject_title(subproject_title)
266    set_target_properties(
267        ${distribution_target}
268        install-${distribution_target}
269        install-${distribution_target}-stripped
270      PROPERTIES
271        FOLDER "${subproject_title}/Distribution"
272    )
273
274    foreach(target ${distribution_components})
275      # Note that some distribution components may not have an actual target, but only an install-FOO target.
276      # This happens for example if a target is an INTERFACE target.
277      if(TARGET ${target})
278        add_dependencies(${distribution_target} ${target})
279      endif()
280
281      if(TARGET install-${target})
282        add_dependencies(install-${distribution_target} install-${target})
283      elseif(TARGET install-llvm-driver AND ${target} IN_LIST LLVM_DRIVER_TOOL_SYMLINKS)
284        add_dependencies(install-${distribution_target} install-llvm-driver)
285      else()
286        message(SEND_ERROR "Specified distribution component '${target}' doesn't have an install target")
287      endif()
288
289      if(TARGET install-${target}-stripped)
290        add_dependencies(install-${distribution_target}-stripped install-${target}-stripped)
291      elseif(TARGET install-llvm-driver-stripped AND ${target} IN_LIST LLVM_DRIVER_TOOL_SYMLINKS)
292        add_dependencies(install-${distribution_target}-stripped install-llvm-driver-stripped)
293      else()
294        message(SEND_ERROR
295                "Specified distribution component '${target}' doesn't have an install-stripped target."
296                " Its installation target creation should be changed to use add_llvm_install_targets,"
297                " or you should manually create the 'install-${target}-stripped' target.")
298      endif()
299    endforeach()
300  endforeach()
301endfunction()
302