# Copyright 2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: llvm-r2.eclass # @MAINTAINER: # Michał Górny # @AUTHOR: # Michał Górny # @SUPPORTED_EAPIS: 8 # @PROVIDES: llvm-utils # @BLURB: Provide LLVM_SLOT to build against slotted LLVM # @DESCRIPTION: # An eclass to reliably depend on a set of LLVM-related packages # in a matching slot. To use the eclass: # # 1. Set LLVM_COMPAT to the list of supported LLVM slots. # # 2. Use llvm_gen_dep and/or LLVM_USEDEP to add appropriate # dependencies. # # 3. Use llvm-r2_pkg_setup, llvm_chost_setup, llvm_cbuild_setup, # get_llvm_prefix or LLVM_SLOT. # # The eclass sets IUSE and REQUIRED_USE. The flag corresponding # to the newest supported stable LLVM slot (or the newest testing, # if no stable slots are supported) is enabled by default. # # Note that the eclass aims for a best-effort support of CHOST builds # (i.e. compiling/linking against LLVM) and CBUILD use (i.e. calling # LLVM tools at build time). You need to determine what the package # in question needs, and put the appropriate packages in DEPEND and/or # BDEPEND appropriately. # # Example: # @CODE # LLVM_COMPAT=( {16..18} ) # # inherit llvm-r2 # # DEPEND=" # dev-libs/libfoo[${LLVM_USEDEP}] # $(llvm_gen_dep ' # llvm-core/clang:${LLVM_SLOT}= # llvm-core/llvm:${LLVM_SLOT}= # ') # " # @CODE case ${EAPI} in 8) ;; *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; esac if [[ -z ${_LLVM_R2_ECLASS} ]]; then _LLVM_R2_ECLASS=1 inherit llvm-utils # == internal control knobs == # @ECLASS_VARIABLE: _LLVM_OLDEST_SLOT # @INTERNAL # @DESCRIPTION: # Oldest supported LLVM slot. This is used to automatically filter out # unsupported LLVM_COMPAT values. _LLVM_OLDEST_SLOT=15 # @ECLASS_VARIABLE: _LLVM_NEWEST_STABLE # @INTERNAL # @DESCRIPTION: # The newest stable LLVM version. Versions newer than that won't # be automatically enabled via USE defaults. _LLVM_NEWEST_STABLE=19 # == control variables == # @ECLASS_VARIABLE: LLVM_COMPAT # @PRE_INHERIT # @REQUIRED # @DESCRIPTION: # A list of LLVM slots supported by the package, oldest to newest. # # Example: # @CODE # LLVM_COMPAT=( {15..17} ) # @CODE # @ECLASS_VARIABLE: LLVM_OPTIONAL # @PRE_INHERIT # @DEFAULT_UNSET # @DESCRIPTION: # If set to a non-empty value, disables setting REQUIRED_USE # and exporting pkg_setup. You have to add LLVM_REQUIRED_USE and call # pkg_setup manually, with appropriate USE conditions. # == global metadata == # @ECLASS_VARIABLE: LLVM_REQUIRED_USE # @OUTPUT_VARIABLE # @DESCRIPTION: # An eclass-generated REQUIRED_USE string that enforces selecting # exactly one slot. It LLVM_OPTIONAL is set, it needs to be copied # into REQUIRED_USE, under appropriate USE conditions. Otherwise, # it is added automatically. # @ECLASS_VARIABLE: LLVM_USEDEP # @OUTPUT_VARIABLE # @DESCRIPTION: # An eclass-generated USE dependency string that can be applied to other # packages using the same eclass, to enforce a LLVM slot match. _llvm_set_globals() { debug-print-function ${FUNCNAME} "$@" if [[ ${LLVM_COMPAT@a} != *a* ]]; then die "LLVM_COMPAT must be set to an array before inheriting ${ECLASS}" fi local stable=() unstable=() local x for x in "${LLVM_COMPAT[@]}"; do if [[ ${x} -gt ${_LLVM_NEWEST_STABLE} ]]; then unstable+=( "${x}" ) elif [[ ${x} -ge ${_LLVM_OLDEST_SLOT} ]]; then stable+=( "${x}" ) fi done _LLVM_SLOTS=( "${stable[@]}" "${unstable[@]}" ) if [[ ! ${_LLVM_SLOTS[@]} ]]; then die "LLVM_COMPAT does not contain any valid versions (all older than ${_LLVM_OLDEST_SLOT}?)" fi if [[ ${stable[@]} ]]; then # If there is at least one stable slot supported, then enable # the newest stable slot by default. IUSE="+llvm_slot_${stable[-1]}" unset 'stable[-1]' else # Otherwise, enable the "oldest" ~arch slot. We really only # expect a single ~arch version, so this primarily prevents # defaulting to non-keyworded slots. IUSE="+llvm_slot_${unstable[0]}" unset 'unstable[0]' fi local nondefault=( "${stable[@]}" "${unstable[@]}" ) IUSE+=" ${nondefault[*]/#/llvm_slot_}" local flags=( "${_LLVM_SLOTS[@]/#/llvm_slot_}" ) LLVM_REQUIRED_USE="^^ ( ${flags[*]} )" local usedep_flags=${flags[*]/%/(-)?} LLVM_USEDEP=${usedep_flags// /,} readonly LLVM_REQUIRED_USE LLVM_USEDEP if [[ ! ${LLVM_OPTIONAL} ]]; then REQUIRED_USE=${LLVM_REQUIRED_USE} fi } _llvm_set_globals unset -f _llvm_set_globals # == metadata helpers == # @FUNCTION: llvm_gen_dep # @USAGE: # @DESCRIPTION: # Output a dependency block, repeating "" conditionally # to all llvm_slot_* USE flags. Any occurences of '${LLVM_SLOT}' # within the block will be substituted for the respective slot. # # Example: # @CODE # DEPEND=" # $(llvm_gen_dep ' # llvm-core/clang:${LLVM_SLOT}= # llvm-core/llvm:${LLVM_SLOT}= # ') # " # @CODE llvm_gen_dep() { debug-print-function ${FUNCNAME} "$@" [[ ${#} -ne 1 ]] && die "Usage: ${FUNCNAME} " local dep=${1} local slot for slot in "${_LLVM_SLOTS[@]}"; do echo "llvm_slot_${slot}? ( ${dep//\$\{LLVM_SLOT\}/${slot}} )" done } # == ebuild helpers == # @FUNCTION: get_llvm_prefix # @USAGE: [-b|-d] # @DESCRIPTION: # Output the path to the selected LLVM slot. # # With no option or "-d", the path is prefixed by ESYSROOT. LLVM # dependencies should be in DEPEND then. # # With "-b" option, the path is prefixed by BROOT. LLVM dependencies # should be in BDEPEND then. get_llvm_prefix() { debug-print-function ${FUNCNAME} "$@" [[ ${#} -gt 1 ]] && die "Usage: ${FUNCNAME} [-b|-d]" local prefix case ${1--d} in -d) prefix=${ESYSROOT} ;; -b) prefix=${BROOT} ;; *) die "${FUNCNAME}: invalid option: ${1}" ;; esac echo "${prefix}/usr/lib/llvm/${LLVM_SLOT}" } # @FUNCTION: generate_llvm_config # @DESCRIPTION: # Output a llvm-config compatible script that yields paths specific # to the requested LLVM version. generate_llvm_config() { debug-print-function ${FUNCNAME} "$@" local bindir=$(get_llvm_prefix -b)/bin [[ ! -d ${bindir} ]] && bindir= local prefix=$(get_llvm_prefix -d) local includedir=${prefix}/include local libdir=${prefix}/$(get_libdir) local cmake_conf=${libdir}/cmake/llvm/LLVMConfig.cmake if [[ ! -f ${cmake_conf} ]]; then cat <<-EOF #!/usr/bin/env sh echo "LLVM ${LLVM_SLOT} not installed for ABI=${ABI}" >&2 exit 127 EOF return fi local version=$( sed -ne 's:set(LLVM_PACKAGE_VERSION \(.*\)):\1:p' "${cmake_conf}" || die ) [[ -n ${version} ]] || die local cppdefs=$( sed -ne 's:set(LLVM_DEFINITIONS "\(.*\)"):\1:p' "${cmake_conf}" || die ) [[ -n ${cppdefs} ]] || die local targets=$( sed -ne 's:set(LLVM_TARGETS_TO_BUILD \(.*\)):\1:p' "${cmake_conf}" || die ) [[ -n ${targets} ]] || die local libs=$( sed -ne 's:set(LLVM_AVAILABLE_LIBS \(.*\)):\1:p' "${cmake_conf}" || die ) [[ -n ${libs} ]] || die local target_triple=$( sed -ne 's:set(LLVM_TARGET_TRIPLE "\(.*\)"):\1:p' "${cmake_conf}" || die ) [[ -n ${target_triple} ]] || die readarray -d';' -t targets <<<"${targets}" readarray -d';' -t libs <<<"${libs}" # easier than parsing CMake booleans local assertions=OFF [[ ${cppdefs} == *-D_DEBUG* ]] && assertions=ON # major + suffix local shlib_name=LLVM-${version%%.*} [[ ${version} == *git* ]] && shlib_name+="git${version##*git}" local components=( "${libs[@]#LLVM}" "${targets[@]}" # special component groups (grep for add_llvm_component_group) all all-targets engine native nativecodegen ) cat <<-EOF #!/usr/bin/env sh echo "\${0} \${*}" >> "${T}/llvm-config-calls.txt" do_echo() { echo " \${*}" >> "${T}/llvm-config-calls.txt" echo "\${@}" } for arg; do case \${arg} in --assertion-mode) do_echo "${assertions}" ;; --bindir) if [ -n "${bindir}" ]; then do_echo "${bindir}" else do_echo "CBUILD LLVM not available" >&2 exit 1 fi ;; --build-mode) do_echo RelWithDebInfo ;; --build-system) do_echo cmake ;; --cflags|--cppflags) do_echo "-I${includedir} ${cppdefs[*]}" ;; --cmakedir) do_echo "${libdir}/cmake/llvm" ;; --components) do_echo "${components[*],,}" ;; --cxxflags) do_echo "-I${includedir} -std=c++17 ${cppdefs[*]}" ;; --has-rtti) do_echo YES ;; --host-target) do_echo "${target_triple}" ;; --ignore-libllvm) # ignored ;; --includedir) do_echo "${includedir}" ;; --ldflags) do_echo "-L${libdir}" ;; --libdir) do_echo "${libdir}" ;; --libfiles) do_echo "${libdir}/lib${shlib_name}.so" ;; --libnames) do_echo lib${shlib_name}.so ;; --libs) do_echo "-l${shlib_name}" ;; --link-shared|--link-static) # ignored ;; --obj-root|--prefix) do_echo "${prefix}" ;; --shared-mode) do_echo shared ;; --system-libs) do_echo ;; --targets-built) do_echo "${targets[*]}" ;; --version) do_echo "${version}" ;; -*) do_echo "Unsupported option: \${arg}" >&2 exit 1 ;; *) # ignore components, we always return the dylib ;; esac done EOF } # @FUNCTION: llvm_cbuild_setup # @DESCRIPTION: # Prepend the PATH for selected LLVM version in CBUILD. # # This function is meant to be used when the package in question uses # LLVM tools at build time. It is called automatically # by llvm-r2_pkg_setup if LLVM is found installed in BROOT. # # Note that llvm-config from this path must not be used to build against # LLVM, as that will break cross-compilation. llvm_cbuild_setup() { debug-print-function ${FUNCNAME} "$@" local broot_prefix=$(get_llvm_prefix -b) einfo "Using ${broot_prefix} for CBUILD LLVM ${LLVM_SLOT}" [[ -d ${broot_prefix}/bin ]] || die "LLVM ${LLVM_SLOT} not found installed in BROOT (expected: ${broot_prefix}/bin)" llvm_fix_clang_version CC CPP CXX # keep in sync with profiles/features/llvm/make.defaults! llvm_fix_tool_path ADDR2LINE AR AS LD NM OBJCOPY OBJDUMP RANLIB llvm_fix_tool_path READELF STRINGS STRIP llvm_prepend_path -b "${LLVM_SLOT}" } # @FUNCTION: llvm_chost_setup # @DESCRIPTION: # Set the environment for finding selected LLVM slot installed # for CHOST. Create llvm-config wrappers to satisfy legacy lookups. # # This function is meant to be used when the package in question uses # LLVM compiles against and links to LLVM. It is called automatically # by llvm-r2_pkg_setup if LLVM is found installed in ESYSROOT. # # Note that the generated llvm-config may refer to CBUILD installation # of LLVM via --bindir, if it is found available. llvm_chost_setup() { debug-print-function ${FUNCNAME} "$@" local esysroot_prefix=$(get_llvm_prefix -d) einfo "Using ${esysroot_prefix} for CHOST LLVM ${LLVM_SLOT}" [[ -d ${esysroot_prefix} ]] || die "LLVM ${LLVM_SLOT} not found installed in ESYSROOT (expected: ${esysroot_prefix})" # satisfies find_package() in CMake export LLVM_ROOT="${esysroot_prefix}" export Clang_ROOT="${esysroot_prefix}" export LLD_ROOT="${esysroot_prefix}" export MLIR_ROOT="${esysroot_prefix}" export Polly_ROOT="${esysroot_prefix}" # satisfies llvm-config calls, e.g. from meson export PATH="${T}/llvm-bin:${PATH}" mkdir "${T}"/llvm-bin || die # we need to generate it per-ABI, since libdir changes local ABI for ABI in $(get_all_abis); do local path="${T}/llvm-bin/$(get_abi_CHOST)-llvm-config" generate_llvm_config > "${path}" || die chmod +x "${path}" || die done ln -s "$(get_abi_CHOST)-llvm-config" "${T}/llvm-bin/llvm-config" || die } # @FUNCTION: llvm-r2_pkg_setup # @DESCRIPTION: # Handle all supported setup actions automatically. If LLVM is found # installed for CBUILD, call llvm_cbuild_setup. If it is found # installed for CHOST, call llvm_chost_setup. # # This function is a no-op when installing a binary package. # # Note that this function is not exported if LLVM_OPTIONAL is set. # In that case, it needs to be called manually. llvm-r2_pkg_setup() { debug-print-function ${FUNCNAME} "$@" if [[ ${MERGE_TYPE} != binary ]]; then [[ -z ${LLVM_SLOT} ]] && die "LLVM_SLOT unset (broken USE_EXPAND?)" if [[ -d $(get_llvm_prefix -b)/bin ]]; then llvm_cbuild_setup fi if [[ -d $(get_llvm_prefix -d) ]]; then llvm_chost_setup fi fi } fi if [[ ! ${LLVM_OPTIONAL} ]]; then EXPORT_FUNCTIONS pkg_setup fi