# Copyright 2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: zig-utils.eclass # @MAINTAINER: # Eric Joldasov # @AUTHOR: # Eric Joldasov # @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: # @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: # @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: [...] # @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