Skip to content

Commit

Permalink
Add scripts to validate target info.
Browse files Browse the repository at this point in the history
Attempts to recreate the table present in README.md, which
can extract the target information for a single target
or for all targets.

Should currently support the following targets:
- Android
- glibc
- musl
- MinGW
- FreeBSD
- NetBSD
- newlib

Closes cross-rs#686.
  • Loading branch information
Alexhuszagh authored and Emilgardis committed Jun 7, 2022
1 parent cc40325 commit b8ef128
Show file tree
Hide file tree
Showing 2 changed files with 392 additions and 0 deletions.
312 changes: 312 additions & 0 deletions ci/extract_image_info.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
#!/usr/bin/env bash
#
# this script can be invoked as follows:
# export TARGET=aarch64-unknown-linux-musl
# docker run -e TARGET ghcr.io/cross-rs/"$TARGET":main bash -c "`cat extract_target_info.sh`"
#
# the output will be similar to the following:
# | `aarch64-unknown-linux-musl` | 1.2.0 | 9.2.0 | ✓ | 5.1.0 |
#
# in short, it recreates the table except for the test section in README.md.

set -eo pipefail

# shellcheck disable=SC2153
target="${TARGET}"
arch="${target//-*/}"

extract_regex_version() {
# executing shared libraries outputs to stderr, rest to stdout
version="$($1 --version 2>&1)"
if [[ "${version}" =~ $2 ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "Unable to match $3 version info for ${target}." 1>&2
exit 1
fi
}

max_glibc_version() {
# glibc versions have the following format:
# `libc-$major-$minor.so.$abi`, where the `.$abi` may be optional.
# shellcheck disable=SC2207
local -a paths=( $(ls "${1}"/libc-[0-9]*.[0-9]*.so*) )
local major=0
local minor=0
local version
local x
local y
local is_larger

for i in "${!paths[@]}"; do
file=$(basename "${paths[$i]}")
version="${file//libc-/}"
x=$(echo "${version}" | cut -d '.' -f 1)
y=$(echo "${version}" | cut -d '.' -f 2)
is_larger=

if [ "${x}" -gt "${major}" ]; then
is_larger=1
elif [ "${x}" -eq "${major}" ] && [ "${y}" -gt "${minor}" ]; then
is_larger=1
fi

if [ -n "${is_larger}" ]; then
major="${x}"
minor="${y}"
fi
done

echo "${major}.${minor}"
}

# output variables
libc=
cc=
cxx=
qemu=

# select toolchain information
compiler_suffix="${target//-/_}"
cc_var="CC_${compiler_suffix}"
cxx_var="CXX_${compiler_suffix}"
cc_regex=
case "${target}" in
*-*-android*)
cc_regex=".* clang version ([0-9]+.[0-9]+.[0-9]+) .*"
;;
*-*-*-musl*)
cc_regex=".*gcc \(GCC\) ([0-9]+.[0-9]+.[0-9]+).*"
;;
*-*-linux-gnu*)
cc_regex=".*gcc \(.*\) ([0-9]+.[0-9]+.[0-9]+).*"
;;
*-*-windows-gnu*)
# MinGW only reports major/minor versions, and can
# have a -posix or -win32 suffix, eg: 7.5-posix
cc_regex=".*gcc.* \(GCC\) ([0-9]+.[0-9]+).*"
;;
*-*-freebsd)
cc_regex=".*gcc \(GCC\) ([0-9]+.[0-9]+.[0-9]+).*"
;;
*-*-netbsd)
cc_regex=".*gcc \(.*\) ([0-9]+.[0-9]+.[0-9]+).*"
;;
*-none-*)
cc_regex=".*gcc \(.*\) ([0-9]+.[0-9]+.[0-9]+).*"
;;
*)
echo "TODO: Currently unsupported" 1>&2
exit 1
;;
esac

# select qemu arch
qarch="${arch}"
case "${arch}" in
arm*)
qarch="arm"
;;
i*86)
qarch="i386"
;;
powerpc)
qarch="ppc"
;;
powerpc64)
qarch="ppc64"
;;
powerpc64le)
if [ "${CROSS_RUNNER}" = "qemu-user" ]; then
qarch="ppc64le"
else
qarch="ppc64"
fi
;;
riscv64*)
qarch="riscv64"
;;
esac
qemu_regex="qemu-${qarch} version ([0-9]+.[0-9]+.[0-9]+).*"

# evaluate our toolchain info
cc_bin=
cxx_bin=
case "${target}" in
i*86-unknown-linux-gnu | x86_64-unknown-linux-gnu)
cc_bin="gcc"
cxx_bin="g++"
;;
thumb*-none-eabi* | arm*-none-eabi*)
# the ARM/THUMB targets don't have a CC_${compiler_suffix}
cc_bin=arm-none-eabi-gcc
cxx_bin=arm-none-eabi-g++
;;
*)
cc_bin="${!cc_var}"
cxx_bin="${!cxx_var}"
;;

esac
cc=$(extract_regex_version "${cc_bin}" "${cc_regex}" compiler)
if command -v "${cxx_bin}" &>/dev/null; then
# test we can compile a c++ program that requires the c++ stdlib
cat <<EOT >> main.cc
#include <iostream>
int main() {
std::cout << "Testing this" << std::endl;
}
EOT
cxx_flags=()
if [[ "${target}" == *-none-* ]]; then
cxx_flags=("${cxx_flags[@]}" "-nostartfiles")
fi
if "${cxx_bin}" "${cxx_flags[@]}" main.cc >/dev/null 2>&1; then
cxx=1
fi
fi

case "${target}" in
*-*-android*)
libc="${cc}"
;;
*-*-*-musl*)
toolchain_prefix="${!cc_var//-gcc/}"
libdir="/usr/local/${toolchain_prefix}/lib"
libc_regex=".*Version ([0-9]+.[0-9]+.[0-9]+).*"
if [[ "${arch}" = i[3-7]86 ]] || [ "${arch}" == x86_64 ]; then
libc_cmd="${libdir}/libc.so"
else
libc_cmd="qemu-${qarch} ${libdir}/libc.so"
if ! command -v "qemu-${qarch}" &>/dev/null; then
echo "Unable to get qemu version for ${target}: qemu not found." 1>&2
exit 1
fi
fi
libc=$(extract_regex_version "${libc_cmd}" "${libc_regex}" libc)
;;
arm-unknown-linux-gnueabihf)
# this is for crosstool-ng-based images with glibc
libdir="/x-tools/${target}/${target}/sysroot/lib/"
libc=$(max_glibc_version "${libdir}")
;;
i*86-unknown-linux-gnu)
libdir="/lib/x86_64-linux-gnu/"
libc=$(max_glibc_version "${libdir}")
;;
x86_64-unknown-linux-gnu)
libdir="/lib64/"
libc=$(max_glibc_version "${libdir}")
;;
*-*-linux-gnu*)
toolchain_prefix="${!cc_var//-gcc/}"
libdir="/usr/${toolchain_prefix}/lib"
libc=$(max_glibc_version "${libdir}")
;;
*-*-windows-gnu)
# no libc, intentionally omitted.
;;
*-*-freebsd)
# we write the FreeBSD version to /opt/freebsd-version
# the symbol versioning can be found here:
# https://wiki.freebsd.org/SymbolVersioning
version=$(cat /opt/freebsd-version)
if [[ "${version}" =~ ([0-9]+)\.([0-9]+)" ("[A-Za-z]+")" ]]; then
major_version="${BASH_REMATCH[1]}"
minor_version="${BASH_REMATCH[2]}"
case "${major_version}" in
7)
libc="1.0"
;;
8)
libc="1.1"
;;
9)
libc="1.2"
;;
10)
libc="1.3"
;;
11)
libc="1.4"
;;
12)
libc="1.5"
;;
13)
libc="1.6"
;;
*)
echo "Invalid FreeBSD version, got ${major_version}.${minor_version}." 1>&2
exit 1
;;
esac
else
echo "Unable to get libc version for ${target}: invalid FreeBSD release found." 1>&2
exit 1
fi
;;
*-*-netbsd)
# We can read the NetBSD version from the libc symbols.
# The output follows:
# NetBSD 0x00000004 IDENT 902000000 (9.2.0)
libdir="/usr/local/${target}/lib"
version=$(readelf -a "${libdir}"/libc.so | grep NetBSD | head -n 1)
if [[ "${version}" =~ .+" ("([0-9]+)"."([0-9]+)"."([0-9]+)")" ]]; then
major_version="${BASH_REMATCH[1]}"
minor_version="${BASH_REMATCH[2]}"
patch_version="${BASH_REMATCH[3]}"
libc="${major_version}.${minor_version}.${patch_version}"
else
echo "Unable to get libc version for ${target}: invalid NetBSD release found." 1>&2
exit 1
fi
;;
thumb*-none-eabi* | arm*-none-eabi*)
# newlib kinda sucks. just query for the install package
pkg=$(dpkg --get-selections | grep -v deinstall | grep newlib | head -n 1)
pkg=$(echo "${pkg}" | cut -f 1)
version=$(dpkg-query --showformat='${Version}' --show "${pkg}")
if [[ "${version}" =~ ([0-9]+)"."([0-9]+)"."([0-9]+)"+".* ]]; then
major_version="${BASH_REMATCH[1]}"
minor_version="${BASH_REMATCH[2]}"
patch_version="${BASH_REMATCH[3]}"
libc="${major_version}.${minor_version}.${patch_version}"
else
echo "Unable to get libc version for ${target}: invalid NetBSD release found." 1>&2
exit 1
fi
;;
*)
echo "TODO: Currently unsupported" 1>&2
exit 1
;;
esac

if command -v "qemu-${qarch}" &>/dev/null; then
qemu=$(extract_regex_version "qemu-${qarch}" "${qemu_regex}" qemu)
fi

# format our output
printf "| %-36s |" "\`${target}\`"
if [ "$libc" != "" ]; then
printf " %-6s |" "${libc}"
else
printf " N/A |"
fi
if [ "$cc" != "" ]; then
printf " %-7s |" "${cc}"
else
printf " N/A |"
fi
if [ "$cxx" != "" ]; then
printf " ✓ |"
else
printf " |"
fi
if [ "$qemu" != "" ]; then
printf " %-5s |" "${qemu}"
else
printf " N/A |"
fi
printf "\n"
80 changes: 80 additions & 0 deletions ci/extract_target_info.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
#
# this script can be customized with the following env vars:
# CROSS_IMAGE: defaults to `ghcr.io/cross-rs`
# CROSS_CONTAINER_ENGINE: defaults to `docker` or `podman`
# CROSS_IMAGE_VERSION: defaults to `main`.
#
# if no arguments are provided, this script will process all
# images. you can extract target info for specific targets
# by providing them as arguments after the script, for example,
# `./extract_target_info.sh i686-linux-android`.
#
# the output will be similar to the following:
# | `aarch64-unknown-linux-musl` | 1.2.0 | 9.2.0 | ✓ | 5.1.0 |
# | `i686-linux-android` | 9.0.8 | 9.0.8 | ✓ | 5.1.0 |
# | `i686-unknown-linux-musl` | 1.2.0 | 9.2.0 | ✓ | N/A |
# ...
#
# in short, it recreates the table except for the test section in README.md.

# shellcheck disable=SC2207

set -eo pipefail

scriptdir=$(dirname "${BASH_SOURCE[0]}")
scriptdir=$(realpath "${scriptdir}")
project_dir=$(dirname "${scriptdir}")

if [[ -z "$CROSS_IMAGE" ]]; then
CROSS_IMAGE="ghcr.io/cross-rs"
fi
if [[ -z "$CROSS_CONTAINER_ENGINE" ]]; then
if command -v "docker" &>/dev/null; then
CROSS_CONTAINER_ENGINE="docker"
elif command -v "podman" &>/dev/null; then
CROSS_CONTAINER_ENGINE="podman"
else
echo "Unable to find suitable container engine." 1>&2
exit 1
fi
fi
if [[ -z "$CROSS_IMAGE_VERSION" ]]; then
CROSS_IMAGE_VERSION="main"
fi

pull() {
"${CROSS_CONTAINER_ENGINE}" pull "${1}"
}

run() {
TARGET="${1}" "${CROSS_CONTAINER_ENGINE}" run --rm -e TARGET \
-v "${scriptdir}:/ci:ro" "${2}" \
bash -c "/ci/extract_image_info.sh"
}

# parse our CI list, so updating our CI automatically updates our target list.
if [[ $# -eq "0" ]]; then
ci="${project_dir}"/.github/workflows/ci.yml
matrix=$(yq '."jobs"."generate-matrix"."steps".0."env"."matrix"' "${ci}")
targets=($(yq '.[]."target"' <<< "${matrix}"))
else
targets=("${@}")
fi
for target in "${targets[@]}"; do
# can't do MSVC, Darwin, or iOS images.
case "${target}" in
*-msvc | *-darwin | *-apple-ios)
continue
;;
esac

image="${CROSS_IMAGE}"/"${target}":"${CROSS_IMAGE_VERSION}"
if [[ -z "$DEBUG" ]]; then
pull "${image}" >/dev/null 2>&1
run "${target}" "${image}" 2>/dev/null
else
pull "${image}"
run "${target}" "${image}"
fi
done

0 comments on commit b8ef128

Please sign in to comment.