public inbox for gentoo-dev@lists.gentoo.org
 help / color / mirror / Atom feed
From: kangie@gentoo.org
To: gentoo-dev@lists.gentoo.org
Cc: Matt Jolly <kangie@gentoo.org>
Subject: [gentoo-dev] [PATCH 01/10] rust.eclass: Introduce new eclass for slotted Rust
Date: Wed,  6 Nov 2024 21:25:01 +1000	[thread overview]
Message-ID: <20241106112510.1518157-2-kangie@gentoo.org> (raw)
In-Reply-To: <20241106112510.1518157-1-kangie@gentoo.org>

From: Matt Jolly <kangie@gentoo.org>

The rust eclass acts similarly to the llvm eclass.

It works with optional `RUST_{MAX,MIN}_SLOT` variables to
enable ebuilds to trivially generate (and enforce) dependencies
on an appropriate Rust SLOT.

A `RUST_NEEDS_LLVM` variable can be set to have the eclass read
`LLVM_COMPAT` and generate an llvm-r1-USE-gated dependency string
for use in the ebuild, storing the result in `RUST_LLVM_DEP` for
consumption. `llvm_gen_dep` is not suitable; see the eclass for
detail on why a `rust_llvm_gen_dep` was not implemented.

The default `rust_pkg_setup` will prefix the selected slot to
`PATH` and export `RUSTC` and `CARGO` variables pointing to that
slot for ease-of-use.

This should prevent issues like:

Bug: https://bugs.gentoo.org/907492
Bug: https://bugs.gentoo.org/942444
Signed-off-by: Matt Jolly <kangie@gentoo.org>
---
 eclass/rust.eclass | 480 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 480 insertions(+)
 create mode 100644 eclass/rust.eclass

diff --git a/eclass/rust.eclass b/eclass/rust.eclass
new file mode 100644
index 000000000000..bf9c47cd7f4b
--- /dev/null
+++ b/eclass/rust.eclass
@@ -0,0 +1,480 @@
+# Copyright 1999-2024 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+# @ECLASS: rust.eclass
+# @MAINTAINER:
+# Matt Jolly <kangie@gentoo.org>
+# @AUTHOR:
+# Matt Jolly <kangie@gentoo.org>
+# @SUPPORTED_EAPIS: 8
+# @BLURB: Utility functions to build against slotted Rust
+# @DESCRIPTION:
+# An eclass to reliably depend on a Rust or Rust/LLVM combination for
+# a given Rust slot. To use the eclass:
+#
+# 1. If required, set RUST_{MAX,MIN}_SLOT to the range of supported slots.
+# 2. Use rust_gen_deps to add appropriate dependencies. (rust_gen_llvm_deps for LLVM)
+# 3. Use rust_pkg_setup, get_rust_prefix or RUST_SLOT.
+
+# Example use for a package supporting Rust 1.72.0 to 1.82.0:
+# @CODE
+#
+# RUST_MAX_VER="1.82.0"
+# RUST_MIN_VER="1.72.0"
+#
+# inherit meson rust
+#
+# BDEPEND="
+#	$(rust_gen_deps)
+# "
+#
+# # only if you need to define one explicitly
+# pkg_setup() {
+#	rust_pkg_setup
+#	do-something-else
+# }
+# @CODE
+#
+# Example for a package needing Rust w/ a specific target:
+# @CODE
+# inherit meson rust
+#
+# RDEPEND="
+#	$(rust_gen_deps)
+# "
+# DEPEND=${RDEPEND}
+#
+# rust_check_deps() {
+#	local rust_slot rust_type
+#	{ read -r RUST_SLOT; read -r RUST_TYPE; } <<< $(rust_check_usedep ${RUST_SLOT} "clippy,${MULTILIB_USEDEP}")
+#   if [[ -n ${RUST_SLOT} ]] && [[ -n ${RUST_TYPE} ]]; then
+#		return 0
+#	else
+#		return 1
+#	fi
+# }
+# @CODE
+
+case ${EAPI} in
+	8) ;;
+	*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
+esac
+
+if [[ -z ${_RUST_ECLASS} ]]; then
+_RUST_ECLASS=1
+
+# == internal control knobs ==
+
+# @ECLASS_VARIABLE: _RUST_KNOWN_SLOTS
+# @INTERNAL
+# @DESCRIPTION:
+# Definitive list of Rust slots and the associated LLVM slot, newest first.
+declare -A -g -r _RUST_KNOWN_SLOTS=(
+	["1.82.0"]=19
+	["1.81.0"]=18
+	["1.80.1"]=18
+	["1.79.0"]=18
+	["1.77.1"]=17
+	["1.75.0"]=17
+	["1.74.1"]=17
+	["1.71.1"]=16
+)
+
+# @ECLASS_VARIABLE: _RUST_SLOTS_ORDERED
+# @INTERNAL
+# @DESCRIPTION:
+# Array of Rust slots, newest first.
+# While _RUST_KNOWN_SLOTS stores useful info about the relationship between Rust and LLVM slots,
+# this array is used to store the Rust slots in a more convenient order for iteration.
+declare -a -g -r _RUST_SLOTS_ORDERED=(
+	"1.82.0"
+	"1.81.0"
+	"1.80.1"
+	"1.79.0"
+	"1.77.1"
+	"1.75.0"
+	"1.74.1"
+	"1.71.1"
+)
+
+# == control variables ==
+
+# @ECLASS_VARIABLE: RUST_MAX_VER
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Highest Rust slot supported by the package. Needs to be set before
+# rust_pkg_setup is called. If unset, no upper bound is assumed.
+
+# @ECLASS_VARIABLE: RUST_MIN_VER
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Lowest Rust slot supported by the package. Needs to be set before
+# rust_pkg_setup is called. If unset, no lower bound is assumed.
+
+# @eclass-variable: RUST_NEEDS_LLVM
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# If set to a non-empty value generate a llvm_slot_${llvm_slot}? gated
+# dependency block for rust slots in LLVM_COMPAT. This is useful for
+# packages that need a tight coupling between Rust and LLVM but don't
+# really care _which_ version of Rust is selected. Combine with
+# RUST_MAX_VER and RUST_MIN_VER to limit the range of Rust versions
+# that are acceptable. Will `die` if llvm-r1 is not inherited or
+# an invalid combination of RUST and LLVM slots is detected; this probably
+# means that a LLVM slot in LLVM_COMPAT has had all of its Rust slots filtered.
+
+# @ECLASS_VARIABLE: RUST_LLVM_DEP
+# @OUTPUT_VARIABLE
+# @DESCRIPTION:
+# This is an eclass-generated, llvm-r1 USE gated, Rust dependency string
+# for all LLVM implementations listed in LLVM_COMPAT, filtered by
+# RUST_MAX_VER and RUST_MIN_VER.
+
+# == global metadata ==
+
+_rust_set_globals() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	if [[ -n ${RUST_MAX_VER} && -n ${RUST_MIN_VER} ]]; then
+		if ! ver_test ${RUST_MAX_VER} -ge ${RUST_MIN_VER}; then
+			die "RUST_MAX_VER must be greater than or equal to RUST_MIN_VER"
+		fi
+	fi
+
+	# Make an array of slots that are acceptable
+	local acceptable_slots=()
+	local slot
+	# Try to keep this in order of newest to oldest
+	for slot in "${_RUST_SLOTS_ORDERED[@]}"; do
+		if [[ -z "${RUST_MAX_VER}" ]] || ver_test ${slot} -le ${RUST_MAX_VER}; then
+			if [[ -z "${RUST_MIN_VER}" ]] || ver_test ${slot} -ge ${RUST_MIN_VER}; then
+				acceptable_slots+=( "${slot}" )
+			fi
+		fi
+	done
+
+	_RUST_SLOTS=( "${acceptable_slots[@]}" )
+	readonly _RUST_SLOTS
+
+	# The alternative to this monstrosity is to consume _LLVM_SLOTS from llvm-r1.eclass
+	# and have our own `rust_gen_llvm_dep` to programmatically generate the deps.
+	# To be good Gentoo netizens we're not going to consume internal variables from another eclass
+	# without first asking the maintainer of that eclass if it's okay; this should be "good enough".
+	if [[ -n "${RUST_NEEDS_LLVM}" ]]; then
+		# This is a proxy for LLVM_COMPAT, but it doesn't make sense to use the generated
+		# RUST_LLVM_DEPS without llvm-r1 so we're probably fine.
+		if [[ -z ${_LLVM_R1_ECLASS} ]]; then
+			die "${FUNCNAME}: llvm-r1.eclass is required"
+		fi
+
+		local llvm_dep=()
+		local llvm_slot
+		local rust_slot
+		for llvm_slot in ${LLVM_COMPAT[@]}; do
+			# Quick sanity check to make sure that the llvm slot is valid for Rust.
+			if [[ "${_RUST_KNOWN_SLOTS[@]}" == *"${llvm_slot}"* ]]; then
+				# We're working a bit backwards here; iterate over RUST_KNOWN_SLOTS, check the
+				# LLVM slot, and if it matches add this to a new array because it may (and likely will)
+				# match multiple Rust slots.
+				# We already filtered Rust slots that were too old or new for this ebuild!
+				local slot_dep_content=()
+				for rust_slot in "${_RUST_SLOTS[@]}"; do
+					if [[ ${_RUST_KNOWN_SLOTS[${rust_slot}]} == ${llvm_slot} ]]; then
+						slot_dep_content+=( "dev-lang/rust:${rust_slot}[llvm_slot_${llvm_slot}]" )
+						slot_dep_content+=( "dev-lang/rust-bin:${rust_slot}[llvm_slot_${llvm_slot}]" )
+					fi
+
+				done
+				if [ ${#slot_dep_content[@]} -ne 0 ]; then
+					llvm_dep+=( "llvm_slot_${llvm_slot}?  ( || ( ${slot_dep_content[*]} ) )" )
+				else
+					die "${FUNCNAME}: no Rust slots found for LLVM slot ${llvm_slot}"
+				fi
+			fi
+		done
+		RUST_LLVM_DEPS="${llvm_dep[*]}"
+		readonly RUST_LLVM_DEPS
+	fi
+}
+_rust_set_globals
+unset -f _rust_set_globals
+
+# == metadata helpers ==
+
+# @FUNCTION: rust_gen_dep
+# @USAGE: [usedep]
+# @DESCRIPTION:
+# Output a dependency block that will match any suitable Rust SLOT.
+# The dependency will match either sys-devel/rust:${RUST_SLOT}
+# or sys-devel/rust-bin:${RUST_SLOT}.
+#
+# Example:
+# @CODE
+# DEPEND="
+#   $(rust_gen_dep "${USEDEP}")
+# @CODE
+rust_gen_dep() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	if [[ ${#} -gt 1 ]]; then
+		die "Usage: ${FUNCNAME} [usedep]"
+	fi
+
+	local usedep
+
+	if [[ ${#} -eq 1 ]]; then
+		usedep=${1}
+	fi
+
+	local slot
+
+	local RUST_DEPS=()
+	RUST_DEPS+=( "|| (" )
+	for slot in "${_RUST_SLOTS[@]}"; do
+		if [[ -n ${usedep} ]]; then
+			RUST_DEPS+=( "dev-lang/rust:${slot}[${usedep}] dev-lang/rust-bin:${slot}[${usedep}]" )
+		else
+			RUST_DEPS+=( "dev-lang/rust:${slot} dev-lang/rust-bin:${slot}" )
+		fi
+	done
+	RUST_DEPS+=( ")" )
+	echo "${RUST_DEPS[@]}"
+}
+
+# == ebuild helpers ==
+
+# @FUNCTION: rust_check_usedep
+# @USAGE: [-b|-d] slot usedep
+# @DESCRIPTION:
+# Check if a Rust package with the specified SLOT and USE-dependency
+# is installed. If -b is specified, the checks are performed relative
+# to BROOT. If -d is specified, the checks are performed relative to
+# ESYSROOT. -d is the default.
+#
+# Returns SLOT and Rust type if the slot is suitable.
+# Rust type is either "source" or "binary".
+rust_check_usedep()
+{
+	local hv_switch=-d
+	while [[ ${1} == -* ]]; do
+		case ${1} in
+			-b|-d) hv_switch=${1};;
+			*) break;;
+		esac
+		shift
+	done
+
+	local slot=${1}
+	local usedep=${2}
+
+	if ( has_version ${hv_switch} "dev-lang/rust:${slot}[${usedep}]" ||
+		has_version ${hv_switch} "dev-lang/rust-bin:${slot}[${usedep}]" ); then
+		if has_version ${hv_switch} "dev-lang/rust:${slot}[${usedep}]"; then
+			rust_type="source"
+		else
+			rust_type="binary"
+		fi
+		echo ${slot}
+		echo ${rust_type}
+	fi
+}
+
+# @FUNCTION: get_rust_slot
+# @USAGE: [-b|-d]
+# @DESCRIPTION:
+# Find the newest Rust install that is acceptable for the package,
+# and print its version number (i.e. SLOT) and type (source or bin[ary]).
+#
+# If -b is specified, the checks are performed relative to BROOT,
+# and BROOT-path is returned.
+#
+# If -d is specified, the checks are performed relative to ESYSROOT,
+# and ESYSROOT-path is returned. -d is the default.
+#
+# If RUST_M{AX,IN}_SLOT is non-zero, then only Rust versions that
+# are not newer or older than the specified slot(s) will be considered.
+# Otherwise, all Rust versions are be considered acceptable.
+#
+# If the `rust_check_deps()` function is defined within the ebuild, it
+# will be called to verify whether a particular slot is accepable.
+# Within the function scope, RUST_SLOT and LLVM_SLOT will be defined.
+#
+# The function should return a true status if the slot is acceptable,
+# false otherwise. If rust_check_deps() is not defined, the function
+# defaults to checking whether a suitable Rust package is installed.
+get_rust_slot() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	local hv_switch=-d
+	while [[ ${1} == -* ]]; do
+		case ${1} in
+			-b|-d) hv_switch=${1};;
+			*) break;;
+		esac
+		shift
+	done
+
+	local max_slot
+	if [[ -z ${RUST_MAX_VER} ]]; then
+		max_slot=
+	else
+		max_slot=${RUST_MAX_VER}
+	fi
+	local slot
+	local llvm_slot
+
+	if [[ -n ${RUST_NEEDS_LLVM} ]]; then
+		local llvm_r1_slot
+		# quickly get a list of unique llvm slots that we support
+		for llvm_slot in $(echo "${_RUST_KNOWN_SLOTS[@]}"  | tr ' ' '\n' | sort -u | tr '\n' ' '); do
+			if [[ "${LLVM_COMPAT[@]}" == *"${llvm_slot}"* ]]; then
+				# We can check for the USE
+				use llvm_slot_${llvm_slot} && llvm_r1_slot=${llvm_slot}
+			else
+				continue
+			fi
+		done
+		if [[ -z ${llvm_r1_slot} ]]; then
+			die "${FUNCNAME}: no LLVM slot found"
+		fi
+	fi
+
+	# iterate over known slots, newest first
+	for slot in "${_RUST_SLOTS_ORDERED[@]}"; do
+		llvm_slot=${_RUST_KNOWN_SLOTS[${slot}]}
+		# skip higher slots
+		if [[ -n ${max_slot} ]]; then
+			if ver_test ${slot} -eq ${max_slot}; then
+				max_slot=
+			elif ver_test ${slot} -gt ${max_slot}; then
+				continue
+			fi
+		fi
+
+		# If we're in LLVM mode we can skip any slots that don't match the selected USE
+		if [[ -n ${RUST_NEEDS_LLVM} ]]; then
+			if [[ ${llvm_slot} != ${llvm_r1_slot} ]]; then
+				continue
+			fi
+		fi
+
+		if declare -f rust_check_deps >/dev/null; then
+			local RUST_SLOT=${slot}
+			local LLVM_SLOT=${_RUST_KNOWN_SLOTS[${slot}]}
+			rust_check_deps && return
+		else
+			local rust_type
+			# Check for an appropriate Rust version and its type.
+			# Prefer the from-source version "because"
+			if (has_version ${hv_switch} "dev-lang/rust:${slot}" ||
+				has_version ${hv_switch} "dev-lang/rust-bin:${slot}"); then
+				if has_version ${hv_switch} "dev-lang/rust:${slot}"; then
+					rust_type="source"
+				else
+					rust_type="binary"
+				fi
+				echo ${slot}
+				echo ${rust_type}
+				return
+			fi
+		fi
+
+		# We want to process the slot before escaping the loop if we've hit the minimum slot
+		if [[ -n ${RUST_MIN_VER} ]]; then
+			if ver_test ${slot} -eq ${RUST_MIN_VER}; then
+				break
+			fi
+		fi
+
+	done
+
+	# max_slot should have been unset in the iteration
+	if [[ -n ${max_slot} ]]; then
+		die "${FUNCNAME}: invalid max_slot=${max_slot}"
+	fi
+
+	die "No Rust slot${1:+ <= ${1}} satisfying the package's dependencies found installed!"
+}
+
+# @FUNCTION: get_rust_prefix
+# @USAGE: [-b|-d]
+# @DESCRIPTION:
+# Find the newest Rust install that is acceptable for the package,
+# and print an absolute path to it. If both -bin and regular Rust
+# are installed, the regular Rust is preferred.
+#
+# The options and behavior are the same as get_rust_slot.
+get_rust_prefix() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	local prefix=${ESYSROOT}
+	[[ ${1} == -b ]] && prefix=${BROOT}
+
+	local slot rust_type
+	{ read -r slot; read -r rust_type; } <<< $(get_rust_slot)
+
+	if [[ ${rust_type} == "source" ]]; then
+		echo "${prefix}/usr/lib/rust/${slot}/"
+	else
+		echo "${prefix}/opt/rust-bin-${slot}/"
+	fi
+}
+
+# @FUNCTION: rust_prepend_path
+# @USAGE: <slot> <type>
+# @DESCRIPTION:
+# Prepend the path to the specified Rust to PATH and re-export it.
+rust_prepend_path() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	[[ ${#} -ne 2 ]] && die "Usage: ${FUNCNAME} <slot> <type>"
+	local slot=${1}
+
+	local rust_path
+	if [[ ${2} == "source" ]]; then
+		rust_path=${ESYSROOT}/usr/lib/rust/${slot}/bin
+	else
+		rust_path=${ESYSROOT}/opt/rust-bin-${slot}/bin
+	fi
+
+	export PATH="${rust_path}:${PATH}"
+}
+
+# @FUNCTION: rust_pkg_setup
+# @DESCRIPTION:
+# Prepend the appropriate executable directory for the newest
+# acceptable Rust slot to the PATH. If used with LLVM, an appropriate
+# `llvm_pkg_setup` call should be made in addition to this function.
+# For path determination logic, please see the get_rust_prefix documentation.
+#
+# The highest acceptable Rust slot can be set in RUST_MAX_VER variable.
+# If it is unset or empty, any slot is acceptable.
+#
+# The lowest acceptable Rust slot can be set in RUST_MIN_VER variable.
+# If it is unset or empty, any slot is acceptable.
+#
+# `CARGO` and `RUSTC` variables are set for the selected slot and exported.
+#
+# The PATH manipulation is only done for source builds. The function
+# is a no-op when installing a binary package.
+#
+# If any other behavior is desired, the contents of the function
+# should be inlined into the ebuild and modified as necessary.
+rust_pkg_setup() {
+	debug-print-function ${FUNCNAME} "$@"
+
+	if [[ ${MERGE_TYPE} != binary ]]; then
+		{ read -r RUST_SLOT; read -r RUST_TYPE; } <<< $(get_rust_slot)
+		rust_prepend_path "${RUST_SLOT}" "${RUST_TYPE}"
+		CARGO="$(get_rust_prefix)bin/cargo"
+		RUSTC="$(get_rust_prefix)bin/rustc"
+		export CARGO RUSTC
+		einfo "Using Rust ${RUST_SLOT} (${RUST_TYPE})"
+		einfo "CARGO=${CARGO}"
+		einfo "RUSTC=${RUSTC}"
+	fi
+}
+
+fi
+
+EXPORT_FUNCTIONS pkg_setup
-- 
2.47.0



  reply	other threads:[~2024-11-06 11:26 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-11-06 11:25 [gentoo-dev] [PATCH 00/10] new eclass: rust; slotting dev-lang/rust{-bin} kangie
2024-11-06 11:25 ` kangie [this message]
2024-11-07 17:56   ` [gentoo-dev] [PATCH 01/10] rust.eclass: Introduce new eclass for slotted Rust Joonas Niilola
2024-11-06 11:25 ` [gentoo-dev] [PATCH 02/10] cargo: update for rust eclass kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 03/10] dev-lang/rust: port to llvm-r1 and slot (-r100) kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 04/10] profiles/arch/mips: use.mask system-llvm on rust-1.71.1-r100 kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 05/10] dev-lang/rust-bin: llvm-r1 and slot (-r100) kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 06/10] dev-lang/rust{,-bin}: -r100: Drop the `profiler` USE kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 07/10] www-client/chromium: example chromium with slotted rust kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 08/10] www-client/firefox: add 132.0-r1 - rust and llvm-r1 eclasses kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 09/10] gnome-base/librsvg: rust eclass kangie
2024-11-06 11:25 ` [gentoo-dev] [PATCH 10/10] net-libs/rustls-ffi: rust slot kangie
2024-11-07 16:49 ` [gentoo-dev] [PATCH 00/10] new eclass: rust; slotting dev-lang/rust{-bin} Sam James

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20241106112510.1518157-2-kangie@gentoo.org \
    --to=kangie@gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox