blob: 3f36604090575eb4bce2eb11c6d7741792e0e7c2 [file] [log] [blame]
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Distributed under the terms of the GNU General Public License v2
# @ECLASS: cros-rust.eclass
# @MAINTAINER:
# The Chromium OS Authors <chromium-os-dev@chromium.org>
# @BUGREPORTS:
# Please report bugs via https://crbug.com/new (with component "Tools>ChromeOS-Toolchain")
# @VCSURL: https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay/+/master/eclass/@ECLASS@
# @BLURB: Eclass for fetching, building, and installing Rust packages.
if [[ -z ${_ECLASS_CROS_RUST} ]]; then
_ECLASS_CROS_RUST="1"
# Check for EAPI 6+.
case "${EAPI:-0}" in
[012345]) die "unsupported EAPI (${EAPI}) in eclass (${ECLASS})" ;;
esac
# @ECLASS-VARIABLE: CROS_RUST_CRATE_NAME
# @DESCRIPTION:
# The name of the crate used by Cargo. This defaults to the package name.
: "${CROS_RUST_CRATE_NAME:=${PN}}"
# @ECLASS-VARIABLE: CROS_RUST_CRATE_VERSION
# @DESCRIPTION:
# The version of the crate used by Cargo. This defaults to PV. Note that
# cros-rust_get_crate_version can be used to get this information from the
# Cargo.toml but that is only available in src_* functions. Also, for -9999
# ebuilds this is handled in a special way; A symbolic link is used to point to
# the installed crate so it can be removed correctly.
: "${CROS_RUST_CRATE_VERSION:=${PV}}"
# @ECLASS-VARIABLE: CROS_RUST_EMPTY_CRATE
# @PRE_INHERIT
# @DESCRIPTION:
# Indicates that this package is an empty crate for satisfying cargo's
# requirements but will not actually be used during compile time. Used by
# dev-dependencies or crates like winapi.
: "${CROS_RUST_EMPTY_CRATE:=}"
# @ECLASS-VARIABLE: CROS_RUST_EMPTY_CRATE_FEATURES
# @PRE_INHERIT
# @DESCRIPTION:
# Array of Cargo features emitted into the Cargo.toml of an empty crate. Allows
# downstream crates to depend on this crate with the given features enabled.
if [[ ! -v CROS_RUST_EMPTY_CRATE_FEATURES ]]; then
CROS_RUST_EMPTY_CRATE_FEATURES=()
fi
# @ECLASS-VARIABLE: CROS_RUST_OVERFLOW_CHECKS
# @PRE_INHERIT
# @DESCRIPTION:
# Enable integer overflow checks for this package. Packages that wish to
# disable integer overflow checks should set this value to 0. Integer overflow
# checks are always enabled when the cros-debug flag is set.
: "${CROS_RUST_OVERFLOW_CHECKS:=1}"
# @ECLASS-VARIABLE: CROS_RUST_REMOVE_DEV_DEPS
# @PRE_INHERIT
# @DESCRIPTION:
# Removes all the dev-dependencies from the Cargo.toml. This can break circular
# dependencies and help minimize how many dependent packages need to be added.
: "${CROS_RUST_REMOVE_DEV_DEPS:=}"
# @ECLASS-VARIABLE: CROS_RUST_REMOVE_TARGET_CFG
# @PRE_INHERIT
# @DESCRIPTION:
# Removes all the target. sections from the Cargo.toml except cfg(unix),
# cfg(linux), cfg(not(windows), and *-linux-gnu. Note that this does not handle
# more complicated cfg strings, so those cases should be handled manually
# instead of using this option.
: "${CROS_RUST_REMOVE_TARGET_CFG:=}"
# @ECLASS-VARIABLE: CROS_RUST_SUBDIR
# @DESCRIPTION:
# Subdir where the package is located. Only used by cros-workon ebuilds.
: "${CROS_RUST_SUBDIR:=${CROS_RUST_CRATE_NAME}}"
# @ECLASS-VARIABLE: CROS_RUST_TESTS
# @DESCRIPTION:
# An array of test executables to be run, which defaults to empty value and is
# set by invoking cros-rust_get_test_executables.
: "${CROS_RUST_TESTS:=}"
# @ECLASS-VARIABLE: CROS_RUST_HOST_TESTS
# @DESCRIPTION:
# An array of test executables that are built for cros-host, which defaults to
# empty value and is set by invoking cros-rust_get_host_test_executables.
# If it is empty when cros-rust_get_test_executables is called, it will be set
# to include tests not compiled for ${CHOST}.
: "${CROS_RUST_HOST_TESTS:=}"
# @ECLASS-VARIABLE: CROS_RUST_PLATFORM_TEST_ARGS
# @DESCRIPTION:
# An array of arguments to pass to platform2_test.py such as --no-ns-net,
# --no-ns-pid, or --run_as_root.
# @ECLASS-VARIABLE: CROS_RUST_TEST_DIRECT_EXEC_ONLY
# @DESCRIPTION:
# If set to yes, run the test only for amd64 and x86 (i.e. no emulation).
: "${CROS_RUST_TEST_DIRECT_EXEC_ONLY:="no"}"
inherit multiprocessing toolchain-funcs cros-constants cros-debug cros-sanitizers
IUSE="amd64 asan cros_host fuzzer lsan +lto msan sccache test tsan ubsan x86"
REQUIRED_USE="?? ( asan lsan msan tsan )"
EXPORT_FUNCTIONS pkg_setup src_unpack src_prepare src_configure src_compile src_test src_install pkg_preinst pkg_postinst pkg_prerm
DEPEND="
>=virtual/rust-1.39.0:=
"
ECARGO_HOME="${WORKDIR}/cargo_home"
CROS_RUST_REGISTRY_BASE="/usr/lib/cros_rust_registry"
CROS_RUST_REGISTRY_DIR="${CROS_RUST_REGISTRY_BASE}/store"
CROS_RUST_REGISTRY_INST_DIR="${CROS_RUST_REGISTRY_BASE}/registry"
# Ignore odr violations in unit tests in asan builds
# (https://github.com/rust-lang/rust/issues/41807).
export ASAN_OPTIONS="detect_odr_violation=0"
# @FUNCTION: cros-rust_get_reg_lock
# @DESCRIPTION:
# Return the path to the rust registry lock file used to prevent races. A
# function is required to support binary packages shared across boards by moving
# the reference to PORTAGE_TMPDIR out of global scope.
cros-rust_get_reg_lock() {
echo "${PORTAGE_TMPDIR}/cros-rust-registry/lock"
}
# @FUNCTION: cros-rust_get_sccache_dir
# @DESCRIPTION:
# Return the path to the directory used for the sccache cache. This cannot be in
# global scope for the reason above.
cros-rust_get_sccache_dir() {
echo "${PORTAGE_TMPDIR}/sccache"
}
# @FUNCTION: cros-rust_pkg_setup
# @DESCRIPTION:
# Sets up the package. Particularly, makes sure the rust registry lock exits.
cros-rust_pkg_setup() {
debug-print-function "${FUNCNAME[0]}" "$@"
# This triggers a linter error SC2154 which says:
# "EBUILD_PHASE_FUNC is used but not defined inside this file"
# Since EBUILD_PHASE_FUNC comes from outside the file, that's ok
# shellcheck disable=SC2154
if [[ "${EBUILD_PHASE_FUNC}" != "pkg_setup" ]]; then
die "${FUNCNAME[0]}() should only be used in pkg_setup() phase"
fi
_cros-rust_prepare_lock "$(cros-rust_get_reg_lock)"
# This is needed for CROS_WORKON_INCREMENTAL_BUILD to be honored.
if [[ -n "${CROS_WORKON_PROJECT}" ]]; then
cros-workon_pkg_setup
fi
local sccache_dir="$(cros-rust_get_sccache_dir)"
addwrite "${sccache_dir}"
mkdir -p "${sccache_dir}"
chmod 755 "${sccache_dir}"
chown "${PORTAGE_USERNAME}:${PORTAGE_GRPNAME}" "${sccache_dir}" "${sccache_dir%/*}"
}
# @FUNCTION: cros-rust_src_unpack
# @DESCRIPTION:
# Unpacks the package
cros-rust_src_unpack() {
debug-print-function "${FUNCNAME[0]}" "$@"
# If this is a cros-workon ebuild and hasn't been unpacked, then unpack it.
if [[ -n "${CROS_WORKON_PROJECT}" && ! -e "${S}" ]]; then
cros-workon_src_unpack
S+="/${CROS_RUST_SUBDIR}"
fi
local archive
for archive in ${A}; do
case "${archive}" in
*.crate)
ebegin "Unpacking ${archive}"
ln -s "${DISTDIR}/${archive}" "${archive}.tar"
unpack "./${archive}.tar"
rm "${archive}.tar"
eend $?
;;
*)
unpack "${archive}"
;;
esac
done
if [[ "${CROS_RUST_EMPTY_CRATE}" == "1" ]]; then
# Generate an empty Cargo.toml and src/lib.rs for this crate.
mkdir -p "${S}/src"
cat <<- EOF >> "${S}/Cargo.toml"
[package]
name = "${CROS_RUST_CRATE_NAME}"
version = "${CROS_RUST_CRATE_VERSION}"
authors = ["The Chromium OS Authors"]
[features]
EOF
if [[ "$(declare -p CROS_RUST_EMPTY_CRATE_FEATURES 2> /dev/null)" != "declare -a"* ]]; then
eerror "CROS_RUST_EMPTY_CRATE_FEATURES must be an array"
die
fi
local feature
for feature in "${CROS_RUST_EMPTY_CRATE_FEATURES[@]}"; do
echo "${feature} = []" >> "${S}/Cargo.toml"
done
touch "${S}/src/lib.rs"
fi
# Set up the cargo config.
mkdir -p "${ECARGO_HOME}"
cat <<- EOF > "${ECARGO_HOME}/config"
[source.chromeos]
directory = "${SYSROOT}${CROS_RUST_REGISTRY_INST_DIR}"
[source.crates-io]
replace-with = "chromeos"
local-registry = "/nonexistent"
[build]
jobs = $(makeopts_jobs)
[target.${CHOST}]
linker = "$(tc-getCC)"
EOF
# When the target environment is different from the host environment,
# add a setting for the target environment.
if tc-is-cross-compiler; then
cat <<- EOF >> "${ECARGO_HOME}/config"
[target.${CBUILD}]
linker = "$(tc-getBUILD_CC)"
EOF
fi
# Tell cargo not to use terminal colors if NOCOLOR is set.
if [[ "${NOCOLOR}" == true || "${NOCOLOR}" == yes ]]; then
cat <<- EOF >> "${ECARGO_HOME}/config"
[term]
color = "never"
EOF
fi
}
# @FUNCTION: cros-rust_src_prepare
# @DESCRIPTION:
# Prepares the src. This function supports "# provided by ebuild" macro and
# "# ignored by ebuild" macro for replacing and removing path dependencies
# with ones provided by their ebuild in Cargo.toml
# and Cargo.toml will be modified in place. If the macro is used in
# ${S}/Cargo.toml, CROS_WORKON_OUTOFTREE_BUILD can't be set to 1 in its ebuild.
cros-rust_src_prepare() {
debug-print-function "${FUNCNAME[0]}" "$@"
if grep -q "# provided by ebuild" "${S}/Cargo.toml"; then
# This triggers a linter error SC2154 which says:
# "CROS_WORKON_OUTOFTREE_BUILD is used but not defined inside this file"
# Since CROS_WORKON_OUTOFTREE_BUILD comes from outside the file, that's ok
# shellcheck disable=SC2154
if [[ "${CROS_WORKON_OUTOFTREE_BUILD}" == 1 ]]; then
die "CROS_WORKON_OUTOFTREE_BUILD=1 must not be set when using" \
"\`provided by ebuild\`"
fi
# Replace path dependencies with ones provided by their ebuild.
#
# For local developer builds, we want Cargo.toml to contain path
# dependencies on sibling crates within the same repository or elsewhere
# in the Chrome OS source tree. This enables developers to run `cargo
# build` and have dependencies resolve correctly to their locally
# checked out code.
#
# At the same time, some crates contained within the crosvm repository
# have their own ebuild independent of the crosvm ebuild so that they
# are usable from outside of crosvm. Ebuilds of downstream crates won't
# be able to depend on these crates by path dependency because that
# violates the build sandbox. We perform a sed replacement to eliminate
# the path dependency during ebuild of the downstream crates.
#
# The sed command says: in any line containing `# provided by ebuild`,
# please replace `path = "..."` with `version = "*"`. The intended usage
# is like this:
#
# [dependencies]
# data_model = { path = "../data_model" } # provided by ebuild
#
sed -i \
'/# provided by ebuild$/ s/path = "[^"]*"/version = "*"/' \
"${S}/Cargo.toml" || die
fi
if grep -q "# ignored by ebuild" "${S}/Cargo.toml"; then
if [[ "${CROS_WORKON_OUTOFTREE_BUILD}" == 1 ]]; then
die "CROS_WORKON_OUTOFTREE_BUILD=1 must not be set when using" \
"\`ignored by ebuild\`"
fi
# Emerge ignores "out-of-sandbox" [patch.crates-io] lines in
# Cargo.toml.
sed -i \
'/# ignored by ebuild/d' \
"${S}/Cargo.toml" || die
fi
# Remove dev-dependencies and target.cfg sections within the Cargo.toml file
#
# The awk program reads the file line by line. If any line matches one of the
# matched section headers, it will skip every line a new section header is
# found that does not match one of the matched section headers.
#
# Awk cannot do in-place editing, so we write the result to a temporary
# file before replacing the input with that temp file.
if [[ "${CROS_RUST_REMOVE_DEV_DEPS}" == 1 ]] || [[ "${CROS_RUST_REMOVE_TARGET_CFG}" == 1 ]]; then
awk -v rm_dev_dep="${CROS_RUST_REMOVE_DEV_DEPS}" \
-v rm_target_cfg="${CROS_RUST_REMOVE_TARGET_CFG}" \
'{
# Stop skipping for a new section header, but check for another match.
if ($0 ~ /^\[/) {
skip = 0
}
# If rm_dev_dep is set, match section headers of the following forms:
# [token.dev-dependencies]
# [dev-dependencies.token]
# [dev-dependencies]
if (rm_dev_dep && ($0 ~ /^\[([^][]+\.)?dev-dependencies(\.[^][]+)?\]$/)) {
skip = 1
next
}
# If rm_target_cfg is set, match section headers prefixed by `[target.`,
# but exclude matches that contain any of `cfg(unix`, `cfg(linux`,
# `cfg(not(windows)`, or `-linux-gnu`.
if (rm_target_cfg && ($0 ~ /^\[target[.]/) && ($0 !~ /cfg[(](unix|linux|not[(]windows[)])|-linux-gnu/)) {
skip = 1
next
}
if (skip == 0) {
print
}
}' "${S}/Cargo.toml" > "${S}/Cargo.toml.stripped" || die
mv "${S}/Cargo.toml.stripped" "${S}/Cargo.toml"|| die
fi
default
}
# @FUNCTION: cros-rust_src_configure
# @DESCRIPTION:
# Configures the source and exports any environment variables needed during the
# build.
cros-rust_src_configure() {
debug-print-function "${FUNCNAME[0]}" "$@"
sanitizers-setup-env
cros-debug-add-NDEBUG
if [[ -n "${CROS_WORKON_PROJECT}" ]]; then
# Use a sub directory to avoid unintended interactions with platform.eclass.
export CARGO_TARGET_DIR="$(cros-workon_get_build_dir)/cros-rust"
mkdir -p "${CARGO_TARGET_DIR}"
else
export CARGO_TARGET_DIR="${WORKDIR}"
fi
export CARGO_HOME="${ECARGO_HOME}"
export HOST="${CBUILD}"
export HOST_CC="$(tc-getBUILD_CC)"
# PKG_CONFIG_ALLOW_CROSS is required by pkg-config.
# https://github.com/rust-lang/pkg-config-rs/issues/41.
# Since cargo will overwrites $HOST with "" when building pkg-config, we
# need to set it regardless of the value of tc-is-cross-compiler here.
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG="$(tc-getPKG_CONFIG)"
export SCCACHE_DIR="$(cros-rust_get_sccache_dir)"
export TARGET="${CHOST}"
export TARGET_CC="$(tc-getCC)"
# There is a memory leak in libbacktrace:
# https://github.com/rust-lang/rust/issues/59125
cros-rust_use_sanitizers || export RUST_BACKTRACE=1
# We want debug info even in release builds.
local rustflags=(
-Cdebuginfo=2
-Copt-level=3
)
if use lto
then
rustflags+=( -Clto=thin )
# rustc versions >= 1.45 support -Cembed-bitcode, which Cargo sets to
# no because it does not know that we want to use LTO.
# Because -Clto requires -Cembed-bitcode=yes, set it explicitly.
if [[ $(rustc --version | awk '{ gsub(/\./, ""); print $2 }') -ge 1450 ]]
then
rustflags+=( -Cembed-bitcode=yes )
fi
fi
# We don't want to abort during tests.
use test || rustflags+=( -Cpanic=abort )
if use cros-debug || [[ "${CROS_RUST_OVERFLOW_CHECKS}" == "1" ]]; then
rustflags+=( -Coverflow-checks=on )
fi
use cros-debug && rustflags+=( -Cdebug-assertions=on )
# Rust compiler is not exporting the __asan_* symbols needed in
# asan builds. Force export-dynamic linker flag to export __asan_* symbols
# https://crbug.com/1085546
use asan && rustflags+=( -Csanitizer=address -Clink-arg="-Wl,-export-dynamic" )
use lsan && rustflags+=( -Csanitizer=leak )
use msan && rustflags+=( -Csanitizer=memory -Clink-arg="-Wl,--allow-shlib-undefined")
use tsan && rustflags+=( -Csanitizer=thread )
use ubsan && rustflags+=( -Clink-arg=-fsanitize=undefined )
if use fuzzer; then
rustflags+=(
--cfg fuzzing
-Cpasses=sancov
-Cllvm-args=-sanitizer-coverage-level=4
-Cllvm-args=-sanitizer-coverage-inline-8bit-counters
-Cllvm-args=-sanitizer-coverage-trace-compares
-Cllvm-args=-sanitizer-coverage-pc-table
-Cllvm-args=-sanitizer-coverage-trace-divs
-Cllvm-args=-sanitizer-coverage-trace-geps
-Cllvm-args=-sanitizer-coverage-prune-blocks=0
-Clink-arg="-Wl,--no-gc-sections"
)
fi
local sccache="/usr/bin/sccache"
if use sccache; then
export RUSTC_WRAPPER="${sccache}"
addwrite "$(cros-rust_get_sccache_dir)"
fi
if [[ -n "${CROS_WORKON_PROJECT}" ]]; then
# Force an update the Cargo.lock file.
ecargo generate-lockfile
if [[ "${CROS_WORKON_INCREMENTAL_BUILD}" == "1" ]]; then
local previous_lockfile="${CARGO_TARGET_DIR}/Cargo.lock.prev"
# If any of the dependencies have changed, clear the incremental results.
if [[ ! -f "${previous_lockfile}" ]] ||
! cmp Cargo.lock "${previous_lockfile}" ; then
# This will print errors for the .crate files, but that is OK.
rm -rf "${CARGO_TARGET_DIR}"
mkdir -p "${CARGO_TARGET_DIR}"
cp Cargo.lock "${previous_lockfile}" || die
fi
fi
else
# Remove 3rd party lockfiles.
rm -f Cargo.lock
fi
# Add EXTRA_RUSTFLAGS to the current rustflags. This lets us emerge rust
# packages with locally exported flags for testing purposes as:
# `EXTRA_RUSTFLAGS="<flags>" emerge-$BOARD <package>`
rustflags+=( "${EXTRA_RUSTFLAGS:=}" )
export RUSTFLAGS="${rustflags[*]}"
default
}
# @FUNCTION: cros-rust_use_sanitizers
# @DESCRIPTION:
# Checks whether sanitizers are being used.
cros-rust_use_sanitizers() {
use_sanitizers || use lsan
}
# @FUNCTION: ecargo
# @USAGE: <args to cargo>
# @DESCRIPTION:
# Call cargo with the specified command line options.
ecargo() {
debug-print-function "${FUNCNAME[0]}" "$@"
addwrite Cargo.lock
# Acquire a shared (read only) lock since this does not modify the registry.
flock --shared "$(cros-rust_get_reg_lock)" cargo -v "$@"
local status="$?"
# This needs to be executed on both success and failure.
local sccache="/usr/bin/sccache"
if use sccache; then
sccache --stop-server
fi
if [[ "${status}" != 0 ]]; then
die
fi
}
# @FUNCTION: write_clippy
# @INTERNAL
# @DESCRIPTION:
# Executes cargo clippy and writes lints to file
_ecargo_write_clippy() {
# TODO(crbug.com/1194200): we should stop using /tmp for this sort of thing
local clippy_output_base="/tmp/cargo_clippy/${CATEGORY}"
mkdir -p "${clippy_output_base}"
# FIXME(crbug.com/1195313): rustc sysroot may not contain dependencies
local sysroot_old="${SYSROOT}"
SYSROOT=$(rustc --print sysroot)
ecargo clippy ---message-format json --target="${CHOST}" --release \
--manifest-path="${S}/Cargo.toml" > "${clippy_output_base}/${PF}.json"
export SYSROOT="${sysroot_old}"
}
# @FUNCTION: ecargo_build
# @USAGE: <args to cargo build>
# @DESCRIPTION:
# Call `cargo build` with the specified command line options.
ecargo_build() {
# Intended use case:
# - Crate A generates sources when it is emerged from input files
# that are only accessible when it emerges.
# - Crate B depends on crate A, and this is reflected in the
# ebuild for crate B.
# (Examples: cros-dbus-bindings or bindgen for *-sys)
#
# The following scenarios are supported and need to work:
# - local `cargo build` for crate A
# - local `cargo build` for crate B
# - emerge A
# - emerge B
#
# Add CROS_RUST environment variable to support the `emerge B`
# case, since crate B can't access pre-generated source
# in emerge, the build.rs script for crate A will skip the
# source generation if both of the following are true:
# - The generated source exists
# - `CROS_RUST=1`
export CROS_RUST="1"
ecargo build --target="${CHOST}" --release "$@"
# FIXME(b/191687433): refactor ENABLE_RUST_CLIPPY to be easier to enable/disable then remove the platform2 check
if [[ -n "${ENABLE_RUST_CLIPPY}" && "${CROS_WORKON_PROJECT}" == "chromiumos/platform2" ]]; then
_ecargo_write_clippy
fi
}
# @FUNCTION: ecargo_build_fuzzer
# @DESCRIPTION:
# Call `cargo build` with fuzzing options enabled.
ecargo_build_fuzzer() {
local fuzzer_libdir="$(dirname "$($(tc-getCC) -print-libgcc-file-name)")"
local fuzzer_arch="${ARCH}"
if [[ "${ARCH}" == "amd64" ]]; then
fuzzer_arch="x86_64"
fi
local link_args=(
-Clink-arg="-L${fuzzer_libdir}"
-Clink-arg="-lclang_rt.fuzzer-${fuzzer_arch}"
-Clink-arg="-lc++"
-Clink-arg="-Wl,-export-dynamic"
)
# The `rustc` subcommand for cargo allows us to set some extra flags for
# the current package without setting them for all `rustc` invocations.
# On the other hand the flags in the RUSTFLAGS environment variable are set
# for all `rustc` invocations.
ecargo rustc --target="${CHOST}" --release "$@" -- "${link_args[@]}"
}
# @FUNCTION: cros_rust_platform_test
# @USAGE: <action> <bin> [-- [<test-args> ...]]
# @DESCRIPTION:
# Invokes platform2_test.py
cros_rust_platform_test() {
local platform2_test_py="${CHROOT_SOURCE_ROOT}/src/platform2/common-mk/platform2_test.py"
local action="$1"
local bin="$2"
if [[ "$#" -gt 2 && "$3" != "--" ]]; then
die "Need to use -- to separate program args"
fi
local cmd=(
"${platform2_test_py}"
--action="${action}"
)
if use cros_host || has "${bin}" "${CROS_RUST_HOST_TESTS[@]}"; then
cmd+=( "--host" )
if [[ "${EAPI}" == "7" ]]; then
cmd+=( --sysroot="${BROOT}" )
fi
else
cmd+=( --sysroot="${SYSROOT}" )
fi
cmd+=( "${CROS_RUST_PLATFORM_TEST_ARGS[@]}" )
if [[ -n "${bin}" ]]; then
# $3 is "--" and anything that follows is passed to the test.
cmd+=(
"--"
"${bin}"
"${@:4}"
)
fi
debug-print-function "${cmd[@]}"
"${cmd[@]}" || die
}
# @FUNCTION: ecargo_test
# @USAGE: <args to cargo test>
# @DESCRIPTION:
# Call `cargo test` with the specified command line options.
ecargo_test() {
local test_dir="${CARGO_TARGET_DIR}/ecargo-test"
if has "--no-run" "$@"; then
debug-print-function ecargo test --target="${CHOST}" --target-dir \
"${test_dir}" --release "$@"
ecargo test --target="${CHOST}" --target-dir "${test_dir}" --release "$@"
else
cros-rust_get_test_executables "$@"
local x=0
for (( x = 0; x <= $#; x++ )); do
if [[ ${!x} == "--" ]]; then
break
fi
done
local test_args=( "${@:x}" )
# Make sure there is a separator before --test-threads.
if [[ "${#test_args[@]}" == 0 ]]; then
test_args=( -- )
fi
# Limit the number of test threads if they are not limited already.
if [[ " ${test_args[*]}" != *" --test-threads"* ]]; then
test_args+=( "--test-threads=$(makeopts_jobs)" )
fi
local testfile
for testfile in "${CROS_RUST_TESTS[@]}"; do
cros_rust_platform_test run "${testfile}" "${test_args[@]}"
done
fi
}
# @FUNCTION: cros-rust_get_test_executables
# @USAGE: <args to cargo test>
# @DESCRIPTION:
# Call `ecargo_test` with '--no-run' and '--message-format=json' arguments.
# Then, use jq to parse and store all the test executables in a global array.
cros-rust_get_test_executables() {
mapfile -t CROS_RUST_TESTS < \
<(ecargo_test --no-run --message-format=json "$@" | \
jq -r 'select(.profile.test == true) | .filenames[]')
# Cargo puts tests not compiled for the SYSROOT in ecargo-test/release.
if [[ -z "${CROS_RUST_HOST_TESTS}" ]]; then
local testfile
for testfile in "${CROS_RUST_TESTS[@]}"; do
if [[ "${testfile}" == "${CARGO_TARGET_DIR}/ecargo-test/release"* ]]; then
CROS_RUST_HOST_TESTS+=( "${testfile}" )
fi
done
fi
}
# @FUNCTION: cros-rust_get_host_test_executables
# @USAGE: <args to cargo test>
# @DESCRIPTION:
# Call `ecargo_test` with '--no-run' and '--message-format=json' arguments.
# Then, use jq to parse and store the test executables in a global array.
cros-rust_get_host_test_executables() {
mapfile -t CROS_RUST_HOST_TESTS < \
<(ecargo_test --no-run --message-format=json "$@" | \
jq -r 'select(.profile.test == true) | .filenames[]')
}
# @FUNCTION: cros-rust_publish
# @USAGE: [crate name] [crate version]
# @DESCRIPTION:
# Install a library crate to the local registry store. Should only be called
# from within a src_install() function.
# This triggers a linter error SC2120 which says:
# "rust_publish references arguments, but none are ever passed"
# In this case, we will use without arguments to get a default value, but other
# usages exist in other files that do use arguments, so there is no problem.
# shellcheck disable=SC2120
cros-rust_publish() {
debug-print-function "${FUNCNAME[0]}" "$@"
if [[ "${EBUILD_PHASE_FUNC}" != "src_install" ]]; then
die "${FUNCNAME[0]}() should only be used in src_install() phase"
fi
local default_version="${CROS_RUST_CRATE_VERSION}"
if [[ "${default_version}" == "9999" ]]; then
# This triggers a linter error SC2119 which says:
# "Use foo "$@" if function's $1 should mean script's $1"
# In this case, cros-rust_get_crate_version without arguments retrieves the
# default value which is desired, so this warning can be ignored.
# shellcheck disable=SC2119
default_version="$(cros-rust_get_crate_version)"
fi
local name="${1:-${CROS_RUST_CRATE_NAME}}"
local version="${2:-${default_version}}"
# Create the .crate file.
ecargo package --allow-dirty --no-metadata --no-verify || die
# Unpack the crate we just created into the directory registry.
local crate="${CARGO_TARGET_DIR}/package/${name}-${version}.crate"
mkdir -p "${D}/${CROS_RUST_REGISTRY_DIR}"
pushd "${D}/${CROS_RUST_REGISTRY_DIR}" > /dev/null || die
tar xf "${crate}" || die
# Calculate the sha256sum since cargo will want this later.
local shasum="$(sha256sum "${crate}" | cut -d ' ' -f 1)"
local dir="${name}-${version}"
local checksum="${T}/${name}-${version}-checksum.json"
# Calculate the sha256 hashes of all the files in the crate.
# This triggers a linter error SC2207 which says:
# "Prefer mapfile or read -a to split command
# output (or quote to avoid splitting)."
# In this case, cros-rust_get_crate_version no argument retrieves the
# default value which is desired, so this warning can be ignored.
# shellcheck disable=SC2207
local files=( $(find "${dir}" -type f) )
[[ "${#files[@]}" == "0" ]] && die "Could not find crate files for ${name}"
# Now start filling out the checksum file.
printf '{\n\t"package": "%s",\n\t"files": {\n' "${shasum}" > "${checksum}"
local idx=0
local f
for f in "${files[@]}"; do
shasum="$(sha256sum "${f}" | cut -d ' ' -f 1)"
printf '\t\t"%s": "%s"' "${f#${dir}/}" "${shasum}" >> "${checksum}"
# The json parser is unnecessarily strict about not allowing
# commas on the last line so we have to track this ourselves.
idx="$((idx+1))"
if [[ "${idx}" == "${#files[@]}" ]]; then
printf '\n' >> "${checksum}"
else
printf ',\n' >> "${checksum}"
fi
done
printf "\t}\n}\n" >> "${checksum}"
popd > /dev/null || die
insinto "${CROS_RUST_REGISTRY_DIR}/${name}-${version}"
newins "${checksum}" .cargo-checksum.json
# We want the Cargo.toml.orig file to be world readable.
fperms 0644 "${CROS_RUST_REGISTRY_DIR}/${name}-${version}/Cargo.toml.orig"
# Symlink the 9999 version to the version installed by the crate.
if [[ "${CROS_RUST_CRATE_VERSION}" == "9999" && "${version}" != "9999" ]]; then
dosym "${name}-${version}" "${CROS_RUST_REGISTRY_DIR}/${name}-9999"
fi
}
# @FUNCTION: cros-rust_get_build_dir
# @DESCRIPTION:
# Return the path to the directory where build artifacts are available.
cros-rust_get_build_dir() {
echo "${CARGO_TARGET_DIR}/${CHOST}/release"
}
# @FUNCTION: cros_rust_is_direct_exec
# @DESCRIPTION:
# Return true if the compiled executables are expected to run on this platform.
cros_rust_is_direct_exec() {
use amd64 || use x86
}
cros-rust_src_compile() {
debug-print-function "${FUNCNAME[0]}" "$@"
# Skip non cros-workon packages.
[[ -z "${CROS_WORKON_PROJECT}" ]] && return 0
ecargo_build "$@"
use test && ecargo_test --no-run "$@"
}
cros-rust_src_test() {
debug-print-function "${FUNCNAME[0]}" "$@"
if [[ "${CROS_RUST_TEST_DIRECT_EXEC_ONLY}" == "yes" ]] && ! cros_rust_is_direct_exec; then
ewarn "Skipping unittests for non-x86: ${PN}"
return 0
fi
cros_rust_platform_test "pre_test"
ecargo_test "$@"
cros_rust_platform_test "post_test"
}
cros-rust_src_install() {
debug-print-function "${FUNCNAME[0]}" "$@"
# This triggers a linter error SC2119 which says:
# "Use cros-rust_publish "$@" if function's $1 should mean script's $1"
# Here we will use without arguments to get a default value so there is no problem
# shellcheck disable=SC2119
cros-rust_publish
}
# @FUNCTION: _cros-rust_prepare_lock
# @INTERNAL
# @USAGE: <path to lock>
# @DESCRIPTION:
# Create the specified lock file. This should only be called inside pkg_*
# functions to ensure the lock file is owned by root. The permissions are set to
# 644 so that $PORTAGE_USERNAME:portage will be able to obtain a shared lock
# inside src_* functions.
_cros-rust_prepare_lock() {
if [[ "$(id -u)" -ne 0 ]]; then
die "_cros-rust_prepare_lock should only be called inside pkg_* functions."
fi
mkdir -p "$(dirname "$1")" || die
chmod 755 "$(dirname "$1")" || die
touch "$1" || die
chmod 644 "$1" || die
}
# @FUNCTION: _cleanup_registry_link
# @INTERNAL
# @USAGE: [crate name] [crate version]
# @DESCRIPTION:
# Unlink a library crate from the local registry. This is repeated in the prerm
# and preinst stages.
_cleanup_registry_link() {
local name="${1:-${CROS_RUST_CRATE_NAME}}"
local version="${2:-${CROS_RUST_CRATE_VERSION}}"
local crate="${name}-${version}"
local crate_dir="${ROOT}${CROS_RUST_REGISTRY_DIR}/${crate}"
if [[ "${version}" == "9999" && -L "${crate_dir}" ]]; then
crate="$(basename "$(readlink -f "${crate_dir}")")"
fi
local registry_dir="${ROOT}${CROS_RUST_REGISTRY_INST_DIR}"
local link="${registry_dir}/${crate}"
# Add a check to avoid spamming when it doesn't exist (e.g. binary crates).
if [[ -L "${link}" ]]; then
einfo "Removing ${crate} from Cargo registry"
# Acquire a exclusive lock since this modifies the registry.
_cros-rust_prepare_lock "$(cros-rust_get_reg_lock)"
# This triggers a linter error SC2016 which says:
# "Expressions don't expand in single quotes, use double quotes for that."
# In this case, though, that is exactly what we want since we need the string
# to be passed to sh without being evaluated first.
# shellcheck disable=SC2016
flock --no-fork --exclusive "$(cros-rust_get_reg_lock)" \
sh -c 'rm -f "$0"' "${link}" || die
fi
}
# @FUNCTION: cros-rust_pkg_preinst
# @USAGE: [crate name] [crate version]
# @DESCRIPTION:
# Make sure a library crate isn't linked in the local registry prior to the
# install step to avoid races.
cros-rust_pkg_preinst() {
debug-print-function "${FUNCNAME[0]}" "$@"
if [[ "${EBUILD_PHASE_FUNC}" != "pkg_preinst" ]]; then
die "${FUNCNAME[0]}() should only be used in pkg_preinst() phase"
fi
_cleanup_registry_link "$@"
}
# @FUNCTION: cros-rust_pkg_postinst
# @USAGE: [crate name] [crate version]
# @DESCRIPTION:
# Install a library crate in the local registry store into the registry,
# making it visible to Cargo.
cros-rust_pkg_postinst() {
debug-print-function "${FUNCNAME[0]}" "$@"
if [[ "${EBUILD_PHASE_FUNC}" != "pkg_postinst" ]]; then
die "${FUNCNAME[0]}() should only be used in pkg_postinst() phase"
fi
local name="${1:-${CROS_RUST_CRATE_NAME}}"
local version="${2:-${CROS_RUST_CRATE_VERSION}}"
local crate="${name}-${version}"
local crate_dir="${ROOT}${CROS_RUST_REGISTRY_DIR}/${crate}"
local registry_dir="${ROOT}${CROS_RUST_REGISTRY_INST_DIR}"
if [[ "${version}" == "9999" && -L "${crate_dir}" ]]; then
crate_dir="$(readlink -f "${crate_dir}")"
crate="$(basename "${crate_dir}")"
fi
# Don't install links for binary-only crates as they won't have any
# library crates to register. This avoids dangling symlinks.
if [[ -e "${crate_dir}" ]]; then
local dest="${registry_dir}/${crate}"
einfo "Linking ${crate} into Cargo registry at ${registry_dir}"
mkdir -p "${registry_dir}"
flock --no-fork --exclusive "$(cros-rust_get_reg_lock)" \
ln -srT "${crate_dir}" "${dest}" || die
fi
}
# @FUNCTION: cros-rust_pkg_prerm
# @USAGE: [crate name] [crate version]
# @DESCRIPTION:
# Unlink a library crate from the local registry.
cros-rust_pkg_prerm() {
debug-print-function "${FUNCNAME[0]}" "$@"
if [[ "${EBUILD_PHASE_FUNC}" != "pkg_prerm" ]]; then
die "${FUNCNAME[0]}() should only be used in pkg_prerm() phase"
fi
_cleanup_registry_link "$@"
}
# @FUNCTION: cros-rust_get_crate_version
# @USAGE: <path to crate>
# @DESCRIPTION:
# Returns the version for a crate by finding the first 'version =' line in the
# Cargo.toml in the crate.
# This triggers a linter error SC2120 which says:
# "rust_get_crate_version references arguments, but none are ever passed"
# In this case, we will use without arguments to get a default value, but other
# usages exist in other files that do use arguments, so there is no problem.
# shellcheck disable=SC2120
cros-rust_get_crate_version() {
local crate="${1:-${S}}"
[[ $# -gt 1 ]] && die "${FUNCNAME[0]}: incorrect number of arguments"
awk '/^version = / { print $3 }' "${crate}/Cargo.toml" | head -n1 | tr -d '"'
}
fi