diff options
Diffstat (limited to 'eclass/zig-utils.eclass')
-rw-r--r-- | eclass/zig-utils.eclass | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/eclass/zig-utils.eclass b/eclass/zig-utils.eclass new file mode 100644 index 000000000000..ca72e89ed6ea --- /dev/null +++ b/eclass/zig-utils.eclass @@ -0,0 +1,538 @@ +# Copyright 2024 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: zig-utils.eclass +# @MAINTAINER: +# Eric Joldasov <bratishkaerik@landless-city.net> +# @AUTHOR: +# Eric Joldasov <bratishkaerik@landless-city.net> +# @SUPPORTED_EAPIS: 8 +# @BLURB: Prepare Zig toolchain and set global variables +# @DESCRIPTION: +# Prepare Zig toolchain and set global variables. +# Supports Zig 0.13+. +# Does not set any default function, ebuilds must call them manually. +# Generally, only "zig-utils_setup" is needed. +# +# Intended to be used by ebuilds that call "zig build-exe/lib/obj" +# or "zig test" directly and by "dev-lang/zig". +# For ebuilds with ZBS (Zig Build System), it's usually better +# to inherit zig.eclass instead, as it has default phases-functions. + +if [[ -z ${_ZIG_UTILS_ECLASS} ]]; then +_ZIG_UTILS_ECLASS=1 + +case ${EAPI} in + 8) ;; + *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; +esac + +inherit edo flag-o-matic linux-info + +# @ECLASS_VARIABLE: ZIG_SLOT +# @PRE_INHERIT +# @REQUIRED +# @DESCRIPTION: +# Zig slot that will be used in "ezig" function. Also, if +# ZIG_OPTIONAL is empty, adds dev-lang/zig and dev-lang/zig-bin +# dependency to BDEPEND. Must be >= "0.13". +# +# Example: +# @CODE +# ZIG_SLOT="0.13" +# @CODE +# +# When a new Zig release occurs, it is advisable for maintainers to +# check whether their ebuild supports that new version. If yes, they +# they should bump ZIG_SLOT to the latest version; if not supported, +# they need to patch any issues with new version and again bump +# ZIG_SLOT. This helps to reduce dependencies on outdated Zig +# versions. +# +# This policy of "1 exclusive Zig slot" will work until it +# stabilizes enough (probably near 1.0), then it will be re-evaluated +# and most likely changed to more common in other eclasses ZIG_MIN/ +# ZIG_MAX form. + +# @ECLASS_VARIABLE: ZIG_OPTIONAL +# @PRE_INHERIT +# @DEFAULT_UNSET +# @DESCRIPTION: +# If set to a non-empty value, all logic in zig-utils and +# zig eclasses will be considered optional. No dependencies +# will be added and no phase functions will be exported. +# +# For zig-utils.eclass users: +# You have to add Zig dependency in your BDEPEND manually and call +# at least "zig-utils_setup" before using "ezig". +# +# For zig.eclass users: see documentation in zig.eclass +# instead. +if [[ ! ${ZIG_OPTIONAL} ]]; then + BDEPEND=" + || ( + dev-lang/zig:${ZIG_SLOT} + dev-lang/zig-bin:${ZIG_SLOT} + ) + " +fi + +# @ECLASS_VARIABLE: ZIG_TARGET +# @DEFAULT_UNSET +# @DESCRIPTION: +# Zig target tuple to use. Has the following format: +# arch-os[.os_version_range]-abi[.abi_version] +# Can be passed as: +# * "-target " option in "zig test" or "zig build-exe/lib/obj", +# * "-Dtarget=" option in "zig build" +# (if project uses "std.Build.standardTargetOptions"). +# +# Can be set by user in make.conf. If not set, then auto-generated by +# "zig-utils_setup". +# +# Example: +# @CODE +# # Autodetected by Zig: +# ZIG_TARGET="native" +# # Machine running Linux x86_64 system, with glibc: +# ZIG_TARGET="x86_64-linux-gnu" +# # Similar to above, but versions are passed explicitly: +# ZIG_TARGET="x86_64-linux.6.1.12...6.6.16-gnu.2.38" +# # Machine running Linux PPC64 little-endian system, with musl +# ZIG_TARGET="powerpc64le-linux-musl" +# @CODE +# +# Note for eclass users: it is discouraged to overwrite ZIG_TARGET +# value by ebuilds. In most cases, if you need to hardcode value for +# -Dtarget, it's better to change "build.zig" code instead to use +# appropriate values. For example, if some build-time executable +# intented for host is compiled for cross-platform target, change in +# build.zig "target" for that executable to be "b.graph.host". +# +# In rare cases, if you really need to hardcode ZIG_TARGET, use this +# syntax before calling `zig-utils_setup` (or `zig_pkg_setup`) to +# allow user override: +# @CODE +# pkg_setup() { +# : "${ZIG_TARGET:=aarch64-freestanding-none}" +# zig_pkg_setup +# } +# @CODE + +# @ECLASS_VARIABLE: ZIG_CPU +# @DEFAULT_UNSET +# @DESCRIPTION: +# Zig target CPU and features to use. Has the following format: +# family_name(\+enable_feature|\-disable_feature)* +# Can be passed as: +# * "-mcpu " option in "zig test" or "zig build-exe/lib/obj", +# * "-Dcpu=" option in "zig build" +# (if project uses "std.Build.standardTargetOptions"). +# +# Can be set by user in make.conf. If not set, then auto-generated by +# "zig-utils_setup". +# +# Example: +# @CODE +# # Autodetected by Zig: +# ZIG_CPU="native" +# # AMD Zen 2 processor +# ZIG_CPU="znver2" +# # x86_64 processor, X87 support enabled, SSE2 support disabled +# ZIG_CPU="x86_64+x87-sse2" +# @CODE +# +# Note for eclass users: it is discouraged to overwrite ZIG_CPU +# value by ebuilds. In most cases, if you need to hardcode value for +# -Dcpu, it's better to change "build.zig" code instead to use +# appropriate values. For example, if some build-time executable +# intented for host is compiled for cross-platform target, change in +# build.zig "target" for that executable to be "b.graph.host". +# +# In rare cases, if you really need to hardcode ZIG_CPU, use this +# syntax before calling `zig-utils_setup` (or `zig_pkg_setup`) to +# allow user override: +# @CODE +# pkg_setup() { +# : "${ZIG_CPU:=apple_m1}" +# zig_pkg_setup +# } +# @CODE + +# @ECLASS_VARIABLE: ZIG_EXE +# @OUTPUT_VARIABLE +# @DESCRIPTION: +# Absolute path to the used Zig executable. +# Set by "zig-utils_setup"/"zig-utils_find_installation". +# +# Please note that when passing one flag several times with different +# values: +# * (only "zig build") in "-Dbar=false -Dbar" form: +# errors due to conflict of flags, +# * (only "zig build") in "-Dbar=false -Dbar=true" form: +# "bar" becomes a list, which is likely not what you want, +# * in "-fbar -fno-bar" form: +# latest value overwrites values before. +# Example above shows only boolean option, but it is same with other +# types of options (enums, "std.zig.BuildId", "std.SemanticVersion", +# integers, strings, etc.). + +# @ECLASS_VARIABLE: ZIG_VER +# @OUTPUT_VARIABLE +# @DESCRIPTION: +# Zig version as reported in dev-lang/zig-${PV} PV part. +# Set by "zig-utils_setup"/"zig-utils_find_installation". +# +# Example: +# @CODE +# 0.13.0 +# @CODE + +# @FUNCTION: zig-utils_c_env_to_zig_target +# @USAGE: <C-style target tuple> <CFLAGS> +# @DESCRIPTION: +# Translates C-style target tuple (like CHOST) and CFLAGS to Zig-style +# target tuple. For full information "zig-utils_c_env_to_zig_cpu" is +# needed, because some information is located in different places in C +# and Zig, for example: +# * Moved from C target to Zig CPU: x86 and ARM families, +# * Moved from CFLAGS to Zig tuple: ARM Thumb mode. +# +# Mostly used during cross-compilation to get target triple if user +# did not set ZIG_TARGET variable, and always during bootstraping Zig. +# +# See ZIG_TARGET description for more information. +zig-utils_c_env_to_zig_target() { + if [[ ${#} -ne 2 ]]; then + die "${FUNCNAME[0]}: expected 2 arguments, got ${#}" + fi + local c_tuple="${1}" + local c_arch="${c_tuple%%-*}" + local c_abi="${c_tuple##*-}" + + local c_flags="${2}" + local c_flags_march="$(CFLAGS="${c_flags}" get-flag march)" + + local arch os abi + + case "${c_arch}" in + i?86) arch=x86;; + arm64) arch=aarch64;; + arm*) + if [[ "${c_flags_march}" == *-m ]]; then + arch=thumb + else + arch=arm + fi + + if [[ "${c_arch}" == *eb ]]; then + arch+="eb" + fi + ;; + *) arch="${c_arch}";; + esac + + case "${c_tuple}" in + *-linux*) os=linux;; + *-apple*) os=macos;; + esac + + case "${c_abi}" in + darwin*) abi=none;; + *) abi="${c_abi}";; + esac + + echo "${arch}-${os}-${abi}" +} + +# @FUNCTION: zig-utils_c_env_to_zig_cpu +# @USAGE: <C-style target tuple> <CFLAGS> +# @DESCRIPTION: +# Translates C-style target tuple (like CHOST) and CFLAGS to Zig-style +# target CPU and features. For full information +# "zig-utils_c_env_to_zig_target" is needed, because some information +# is located in different places in C and Zig, for example: +# * Moved from C target to Zig CPU: x86 and ARM families, +# * Moved from CFLAGS to Zig tuple: ARM Thumb mode. +# +# Used to get target CPU if user did not set ZIG_CPU variable. +# +# See ZIG_CPU description for more information. +zig-utils_c_env_to_zig_cpu() { + if [[ ${#} -ne 2 ]]; then + die "${FUNCNAME[0]}: expected 2 arguments, got ${#}" + fi + local c_tuple="${1}" + local c_arch="${c_tuple%%-*}" + + local c_flags="${2}" + local c_flags_mabi="$(CFLAGS="${c_flags}" get-flag mabi)" + local c_flags_march="$(CFLAGS="${c_flags}" get-flag march)" + local c_flags_mcpu="$(CFLAGS="${c_flags}" get-flag mcpu)" + local c_flags_mfpu="$(CFLAGS="${c_flags}" get-flag mfpu)" + + local base_cpu features="" + + case "${c_arch}" in + x86_64 | i?86) + local c_cpu="${c_flags_march}" + case "${c_cpu}" in + "") base_cpu="${c_arch}";; + *) base_cpu="${c_cpu//[-.]/_}";; + esac + ;; + aarch64 | aarch64_be | arm*) + local c_cpu="${c_flags_mcpu}" + case "${c_cpu}" in + "") base_cpu=generic;; + *) base_cpu="${c_cpu//[-.]/_}";; + esac + + case "${c_flags_march}" in + "") ;; + armv*) + local c_arm_family="${c_flags_march##arm}" + c_arm_family="${c_arm_family//./_}" + c_arm_family="${c_arm_family//-/}" + features+="+${c_arm_family}" + ;; + *) features+="+${c_flags_march}";; + esac + + if [[ "${c_arch}" != aarch64* && "${c_arch}" != arm64 ]]; then + if [[ "${c_flags_mfpu}" == crypto-* ]]; then + c_flags_mfpu="${c_flags_mfpu##crypto-}" + features+="+crypto" + fi + if [[ "${c_flags_mfpu}" == neon-* ]]; then + c_flags_mfpu="${c_flags_mfpu##neon-}" + features+="+neon" + fi + + case "${c_flags_mfpu}" in + "" | auto) ;; + neon) features+="+neon";; + fp16) features+="+fp16";; + fp-armv8) features+="+fp_armv8";; + + vfp | vfpv2) features+="+vfp2";; + + vfp3 | vfpv3) features+="+vfp3";; + vfpv3-fp16) features+="+vfp3sp";; + vfpv3-d16) features+="+vfp3d16";; + vfpv3-d16-fp16) features+="+vfp3d16sp";; + vfpv3xd) features+="+vfp3d16sp";; + vfpv3xd-fp16) features+="+vfp3d16sp+fp16";; + + vfpv4) features+="+vfp4";; + vfpv4-fp16) features+="+vfp4sp";; + vfpv4-d16) features+="+vfp4d16";; + fpv4-sp-fp16) features+="+vfp4d16sp";; + + fpv5-d16) features+="+fp_armv8d16+fp64";; + *) die -n "Unknown ARM FPU: ${c_flags_mfpu}";; + esac + + local is_softfloat="$(CTARGET="${c_tuple}" tc-tuple-is-softfloat)" + case "${is_softfloat}" in + only | yes) features+="+soft_float";; + softfp | no) features+="-soft_float";; + *) die -n "tc-tuple-is-softfloat returned unexpected value: ${is_softfloat}" + esac + fi + ;; + riscv32 | riscv64) + local c_cpu="${c_flags_mcpu}" + case "${c_cpu}" in + "") + case "${c_arch}" in + riscv32) base_cpu=generic_rv32;; + riscv64) base_cpu=generic_rv64;; + esac + ;; + *) base_cpu="${c_cpu//[-.]/_}";; + esac + + local base_isa="${c_flags_march:0:4}" + local extensions="${c_flags_march:4}" + + case "${base_isa}" in + "" | rv32 | rv64) ;; + *) die -n "Unknown RISC-V architecture: ${base_isa}";; + esac + + local extension + while read -n 1 extension; do + case "${extension}" in + "") ;; + g) features+="+i+m+a+f+d+zicsr+zifencei";; + _) die -n "Can't translate multi-letter RISC-V extensions yet";; + *) features+="+${extension}";; + esac + done <<< "${extensions}" + + case "${c_flags_mabi}" in + ilp32d | lp64d) features+="+d";; + ilp32e | lp64e) features+="+e";; + ilp32f | lp64f) features+="+f";; + "" | ilp32 | lp64) ;; + *) die -n "Unknown RISC-V ABI: ${c_flags_mabi}";; + esac + ;; + loongarch64) + local c_cpu="${c_flags_march}" + case "${c_cpu}" in + "") base_cpu=generic_la64;; + *) base_cpu="${c_cpu//[-.]/_}";; + esac + + case "${c_flags_mabi}" in + lp64d) features+="+d";; + lp64f) features+="+f";; + lp64s | "") ;; + *) die -n "Unknown LoongArch ABI: ${c_flags_mabi}";; + esac + ;; + powerpc | powerpcle | powerpc64 | powerpc64le) + local c_cpu="${c_flags_mcpu}" + case "${c_cpu}" in + "") + case "${c_arch}" in + powerpc | powerpcle) base_cpu=ppc;; + powerpc64 | powerpc64le) base_cpu=ppc64;; + esac + ;; + G*) base_cpu="${c_cpu//G/g}";; + powerpcle) base_cpu=ppc;; + powerpc*) base_cpu="${c_cpu//powerpc/ppc}";; + power*) base_cpu="${c_cpu//power/pwr}";; + *) base_cpu="${c_cpu//[-.]/_}";; + esac + ;; + *) base_cpu=generic;; + esac + + echo "${base_cpu}${features}" +} + +# @FUNCTION: zig-utils_find_installation +# @DESCRIPTION: +# Detects suitable Zig installation and sets ZIG_VER and ZIG_EXE +# variables. +# +# See ZIG_EXE and ZIG_VER descriptions for more information. +zig-utils_find_installation() { + # Adapted from https://github.com/gentoo/gentoo/pull/28986 + # Many thanks to Florian Schmaus (Flowdalic)! + + [[ -n "${ZIG_SLOT}" ]] || die "${FUNCNAME[0]}: ZIG_SLOT must be set" + if ver_test "${ZIG_SLOT}" -lt "0.13"; then + die "${ECLASS}: ZIG_SLOT must be >= 0.13, found ${ZIG_SLOT}" + fi + + einfo "Searching Zig ${ZIG_SLOT}..." + + local zig_supported_versions=( + "9999" + "0.13.1" + "0.13.0" + ) + + local base_path="${BROOT}/usr/bin" + + local selected_path selected_ver + for selected_ver in "${zig_supported_versions[@]}"; do + # Check if candidate satisfies ZIG_SLOT condition. + if [[ "${selected_ver}" != "${ZIG_SLOT}"* ]]; then + continue + fi + + # Prefer "dev-lang/zig" over "dev-lang/zig-bin" + local candidate_path + for candidate_path in "${base_path}"/zig{,-bin}-"${selected_ver}"; do + if [[ -x "${candidate_path}" ]]; then + selected_path="${candidate_path}" + break 2 + fi + done + done + + if [[ -z "${selected_path}" ]]; then + die "Could not find (suitable) Zig at \"${base_path}\"" + fi + + declare -g ZIG_EXE="${selected_path}" + declare -g ZIG_VER="${selected_ver}" + # Sanity check, comment from upstream: + # > Check libc++ linkage to make sure Zig was built correctly, + # > but only for "env" and "version" to avoid affecting the + # > startup time for build-critical commands + # > (check takes about ~10 μs) + "${ZIG_EXE}" version > /dev/null || + die "Sanity check failed for \"${ZIG_EXE}\"" +} + +# @FUNCTION: zig-utils_setup +# @DESCRIPTION: +# Checks if running Linux kernel version is supported by Zig. +# Populates ZIG_TARGET, ZIG_CPU, ZIG_EXE and ZIG_VER global +# variables with detected values, or, if user set them already, +# leaves as-is. +zig-utils_setup() { + # Should be first because it sets ZIG_VER which might be used + # in the future when setting ZIG_TARGET and ZIG_CPU variables + # for incompatible versions. + if [[ -z "${ZIG_EXE}" ]]; then + zig-utils_find_installation + fi + + : "${ZIG_CPU:=$(zig-utils_c_env_to_zig_cpu "${CHOST}" "${CFLAGS}")}" + if tc-is-cross-compiler; then + : "${ZIG_TARGET:=$(zig-utils_c_env_to_zig_target "${CHOST}" "${CFLAGS}")}" + else + : "${ZIG_TARGET:=native}" + fi + declare -g ZIG_CPU ZIG_TARGET + + einfo "ZIG_EXE: \"${ZIG_EXE}\"" + einfo "ZIG_VER: ${ZIG_VER}" + einfo "ZIG_TARGET: ${ZIG_TARGET}" + einfo "ZIG_CPU: ${ZIG_CPU}" +} + +# @FUNCTION: ezig +# @USAGE: [<args>...] +# @DESCRIPTION: +# Runs ZIG_EXE with supplied arguments. Dies if ZIG_EXE is not set or +# if command exits with error. Respects `nonfatal`. +# +# Always disables progress tree. By default enables ANSI escape codes +# (colors, etc.), user can set NO_COLOR environment variable to +# disable them. +# +# Note that color support also determines how compile errors will be +# printed: source code lines and reference traces are not available +# when colors are disabled. +ezig() { + # Sync description above and comments below with upstream's + # "std.io.tty.detectConfig". + debug-print-function "${FUNCNAME[0]}" "${@}" + + if [[ -z "${ZIG_EXE}" ]] ; then + die "${FUNCNAME[0]}: ZIG_EXE is not set. Was 'zig-utils_setup' called before using ezig?" + fi + + # Progress tree is helpful indicator in TTY, but unfortunately + # they make Portage logs harder to read in plaintext. + # + # We don't have global toggle for all Zig commands to disable + # progress tree, however we can emulate this using 2 steps. + + # Disable progress tree and colors. Errors are now less detailed. + local -x TERM=dumb + # Re-enable colors. Errors are now yet again detailed for reading. + local -x CLICOLOR_FORCE=1 + # User's NO_COLOR has more priority and can disable colors again. + "${ZIG_EXE}" "${@}" || die -n "Failed to run command: ${ZIG_EXE} ${@}" +} +fi |