458 lines
12 KiB
Bash
Executable File
458 lines
12 KiB
Bash
Executable File
#!/bin/bash --norc
|
|
#
|
|
# To support different arches, compile options, and distributions,
|
|
# Kernel configs are managed with a hierarchy matrix.
|
|
#
|
|
# This script is the helper for parsing and updating the config matrix
|
|
#
|
|
# shellcheck source=./lib.sh
|
|
. "$(dirname "$(realpath "$0")")/lib.sh"
|
|
|
|
CONFIG_PATH=${CONFIG_PATH:-$DISTDIR/configs}
|
|
# shellcheck disable=SC2206
|
|
CONFIG_ARCH=( $SPEC_ARCH )
|
|
CONFIG_SPECS=( "$CONFIG_PATH"/[0-9][0-9]* )
|
|
CONFIG_OUTDIR=$SOURCEDIR
|
|
CONFIG_CACHE=$DISTDIR/workdir/config_cache
|
|
|
|
_get_config_cross_compiler () {
|
|
if [[ "$1" == $(get_native_arch) ]]; then
|
|
:
|
|
elif [[ -d "$TOPDIR/scripts/dummy-tools/" ]]; then
|
|
echo "scripts/dummy-tools/"
|
|
else
|
|
echo "$DISTDIR/scripts/dummy-tools/"
|
|
fi
|
|
}
|
|
|
|
get_config_val() {
|
|
# If it's y/m/n/0-9, return plain text, else get from .config
|
|
#
|
|
# To be more accurate, maybe we need to check right/left-value?
|
|
case $1 in
|
|
n )
|
|
;;
|
|
y|m|[0-9] )
|
|
echo "$1" ;;
|
|
* )
|
|
local _val
|
|
_val=$(grep "^CONFIG_$1=" "$2")
|
|
_val=${_val#*=}
|
|
echo "${_val:-not set}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Eval a Kconfig condition statement
|
|
kconfig_eval() {
|
|
local _line=$1
|
|
|
|
local _if
|
|
# Convert Kconfig cond statement to bash syntax then eval it
|
|
_if=$(echo "$_line" | sed -E \
|
|
-e "s/(\|\||&&|=|!=|<|<=|>|>=|!|\(|\))/ \1 /g" \
|
|
-e "s/([[:alnum:]_-]+)/ \"\$(get_kconf_val \'\1\')\" /g")
|
|
eval "[[ $_if ]]"
|
|
}
|
|
|
|
get_depends() {
|
|
local _conf=$1
|
|
local _deps _line
|
|
|
|
sed -nE "s/^[[:space:]]*depends on //p;" "$CONFIG_CACHE/$_conf"
|
|
}
|
|
|
|
# $1: Parsed Kconfig file
|
|
get_all_depends() {
|
|
local _dep
|
|
local _deps=( )
|
|
|
|
[[ ! -e "$CONFIG_CACHE/$1" ]] && return 1
|
|
|
|
# shellcheck disable=SC2207
|
|
while read -r _dep; do
|
|
if ! [[ "$_dep" ]]; then
|
|
continue
|
|
elif [[ "$_dep" =~ y|n|m ]]; then
|
|
_deps+=( "$_dep" )
|
|
elif [[ "$_dep" =~ [a-zA-Z0-9] ]]; then
|
|
_deps+=( "$_dep[=$(get_config_val "$_dep" "$2")]" )
|
|
else
|
|
_deps+=( "$_dep" )
|
|
fi
|
|
done <<< "$(get_depends "$1" | sed -E \
|
|
-e 's/^([^\(])/(\1/' \
|
|
-e 's/([^\)])$/\1)/' \
|
|
-e '2,$s/^/\&\&/' \
|
|
-e 's/(\|\||&&|=|!=|<|<=|>|>=|!|\(|\)|([[:alnum:]_-]+))/\n\1\n/g')"
|
|
# The regex above simply tokenize the Kconfig expression
|
|
|
|
echo "${_deps[@]}"
|
|
|
|
return 0
|
|
}
|
|
|
|
kconfig_parser() {
|
|
local _arch=$1 _kconfig=$2 _config=$3
|
|
|
|
[[ -s $_kconfig ]] || error "Invalid Kconfig '$_kconfig'"
|
|
[[ -s $_config ]] || error "Invalid config file '$_kconfig'"
|
|
|
|
rm -r "${CONFIG_CACHE:?}" 2>/dev/null
|
|
mkdir -p "$CONFIG_CACHE"
|
|
|
|
local _CONFIG_FILE=$CONFIG_CACHE/_invalid
|
|
local _PARSE_DEPS=()
|
|
|
|
_reader() {
|
|
local _f=$1
|
|
|
|
if [[ $_f == *"\$(SRCARCH)"* ]]; then
|
|
_f=${_f/\$(SRCARCH)/$(get_kernel_src_arch "$_arch")}
|
|
fi
|
|
|
|
local _line
|
|
while :; do
|
|
_line=${_line%%#*}
|
|
case $_line in
|
|
"config "* | "menuconfig "* )
|
|
_CONFIG_FILE=${_line#* }
|
|
_CONFIG_FILE=${_CONFIG_FILE## }
|
|
_CONFIG_FILE=$CONFIG_CACHE/$_CONFIG_FILE
|
|
for _dep in "${_PARSE_DEPS[@]}"; do [[ $_dep ]] && echo "depends on $_dep"; done >> "$_CONFIG_FILE"
|
|
;;
|
|
"source "* )
|
|
_line=${_line#* }
|
|
_line=${_line#\"}
|
|
_line=${_line%\"}
|
|
_reader "$_line"
|
|
;;
|
|
"if "* )
|
|
_line=${_line#if }
|
|
_PARSE_DEPS+=( "${_line}" )
|
|
;;
|
|
"menu "* | "choice" )
|
|
local _sub_dep=""
|
|
while read -r _line; do
|
|
case $_line in
|
|
if*|menu*|config*|end*|source*|choice* ) break ;;
|
|
"depends on"* )
|
|
_line="${_line#depends on}"
|
|
_sub_dep="${_sub_dep:+$_sub_dep && }(${_line## })"
|
|
;;
|
|
"visible if"* )
|
|
_line="${_line#visible if}"
|
|
_sub_dep="${_sub_dep:+$_sub_dep && }(${_line## })"
|
|
;;
|
|
esac
|
|
done
|
|
_PARSE_DEPS+=( "${_sub_dep## }" )
|
|
continue
|
|
;;
|
|
"end"* )
|
|
# We don't care about correctness,
|
|
# it's always paired with previous if/menu/choice
|
|
unset '_PARSE_DEPS[${#_PARSE_DEPS[@]}-1]'
|
|
;;
|
|
'' | ' ' )
|
|
;;
|
|
* )
|
|
echo "$_line" >> "$_CONFIG_FILE"
|
|
esac
|
|
IFS='' read -r _line || break
|
|
done <<< "$(<"$_f")"
|
|
}
|
|
|
|
pushd "$(dirname "$_kconfig")" > /dev/null || die "Failed pushd to '$TOPDIR'"
|
|
|
|
_reader "$_kconfig"
|
|
|
|
popd || die
|
|
}
|
|
|
|
# Call make for config related make target. Will set proper cross compiler and kernel arch name.
|
|
# $1: Target arch.
|
|
config_make() {
|
|
local arch=$1; shift
|
|
local config_cross_compiler config_arch
|
|
|
|
pushd "$TOPDIR" >/dev/null || die "Not in a valid git repo."
|
|
|
|
config_cross_compiler=$(_get_config_cross_compiler "$arch")
|
|
config_arch=$(get_kernel_arch "$arch")
|
|
|
|
if [[ -z "$config_arch" ]]; then
|
|
die "Unsupported arch $arch"
|
|
fi
|
|
|
|
make ARCH="$config_arch" CROSS_COMPILE="$config_cross_compiler" "$@"
|
|
|
|
popd >/dev/null || die "Failed popd"
|
|
}
|
|
|
|
# Dedup, and filter valid config lines, print the sanitized content sorted by config name.
|
|
config_sanitizer() {
|
|
# AWK will add an extra column of CONFIG_NAME for sort to work properly
|
|
LC_ALL=C
|
|
awk '
|
|
/is not set/ {
|
|
split($0, val, "#");
|
|
split(val[2], val);
|
|
configs[val[1]]=$0
|
|
}
|
|
/^CONFIG.*=/ {
|
|
split($0, val, "=");
|
|
if (val[2] == "n")
|
|
configs[val[1]]="# "val[1]" is not set"
|
|
else
|
|
configs[val[1]]=$0
|
|
}
|
|
END {
|
|
for (config in configs) {
|
|
print config " " configs[config]
|
|
}
|
|
}' \
|
|
| LC_ALL=C sort -k 1 \
|
|
| sed -e 's/[ \t]*$//' \
|
|
| cut -d ' ' -f 2-
|
|
}
|
|
|
|
# Iterate the dot product of all config options
|
|
# param: <callback> [<filter>... ]
|
|
# [<filter>... ]: Filters config targets to use. eg. kernel-default-*, kernel-minimal-debug
|
|
# <callback>: A callback function, see below for <callback> params.
|
|
#
|
|
# Will generate a matrix, and call <callback> like this:
|
|
# <callback> <config-target> [ <dir entry of configs in inherit order> ... ]
|
|
#
|
|
# eg.
|
|
# <callback> "kernel-generic-release" "$TOPDIR/00pending/kernel" "$TOPDIR/20preset/generic" "$TOPDIR/50variant/release"
|
|
# <callback> "kernel-generic-debug" "$TOPDIR/00pending/kernel" "$TOPDIR/20preset/generic" "$TOPDIR/50variant/debug"
|
|
# ...
|
|
#
|
|
for_each_config_target () {
|
|
local filters callback
|
|
local target cur_target i j
|
|
|
|
callback=$1; shift
|
|
filters=("$@")
|
|
|
|
_match () {
|
|
[[ ${#filters[@]} -eq 0 ]] && return 0
|
|
|
|
for _f in "${filters[@]}"; do
|
|
# shellcheck disable=SC2053
|
|
[[ $1 == $_f ]] && return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# The '_' is used to bootstrap the file loop
|
|
local -a hierarchy=( _ ) prev_hierarchy target_conf prev_target_conf
|
|
|
|
for folder in "${CONFIG_SPECS[@]}"; do
|
|
prev_hierarchy=( "${hierarchy[@]}" )
|
|
prev_target_conf=( "${target_conf[@]}" )
|
|
|
|
hierarchy=( )
|
|
target_conf=( )
|
|
|
|
i=0
|
|
for config in "$folder"/*; do
|
|
if ! [[ -e "$config/default.config" ]]; then
|
|
error "Invalid config folder $config"
|
|
return 1
|
|
fi
|
|
|
|
j=0
|
|
for target in "${prev_hierarchy[@]}"; do
|
|
cur_target=$target-$(basename "$config")
|
|
hierarchy+=( "$cur_target" )
|
|
# config files can't have spece in their path since the
|
|
# list is split by space, seems no better way
|
|
target_conf[$i]="${prev_target_conf[$j]} $config"
|
|
i=$(( i + 1 ))
|
|
j=$(( j + 1 ))
|
|
done
|
|
done
|
|
done
|
|
|
|
i=0
|
|
for target in "${hierarchy[@]}"; do
|
|
# split target_conf by space is what we want here
|
|
target="${target#_-}"
|
|
# shellcheck disable=SC2086
|
|
_match $target && "$callback" "${target#_-}" ${target_conf[$i]}
|
|
i=$(( i + 1 ))
|
|
done
|
|
}
|
|
|
|
# Iterate populated config files
|
|
# param: <callback> [<filter>... ]
|
|
# [<filter>... ]: Filters config targets to use. eg. kernel-default-*, kernel-minimal-debug
|
|
# <callback>: A callback function, see below for <callback> params.
|
|
#
|
|
# Will generate a matrix, and call <callback> like this:
|
|
# <callback> <arch> <config-file> [ <backing config files in inherit order>... ]
|
|
#
|
|
# eg.
|
|
# <callback> "x86_64" "kernel-generic-release" \
|
|
# "$TOPDIR/00pending/kernel/default.config" "$TOPDIR/00pending/kernel/x86_64.config" \
|
|
# "$TOPDIR/20preset/generic/default.config" "$TOPDIR/20preset/generic/x86_64.config" \
|
|
# "$TOPDIR/50variant/release/default.config" "$TOPDIR/50variant/release/x86_64.config"
|
|
# <callback> "aarch64" "kernel-generic-release" \
|
|
# "$TOPDIR/00pending/kernel/default.config" "$TOPDIR/00pending/kernel/aarch64.config" \
|
|
# "$TOPDIR/20preset/generic/default.config" "$TOPDIR/20preset/generic/aarch64.config" \
|
|
# "$TOPDIR/50variant/release/default.config" "$TOPDIR/50variant/release/aarch64.config"
|
|
# ...
|
|
#
|
|
for_each_config_product () {
|
|
local _wrapper_cb=$1; shift
|
|
|
|
_wrapper () {
|
|
local target=$1; shift
|
|
local files=( "$@" )
|
|
|
|
for arch in "${CONFIG_ARCH[@]}"; do
|
|
local config_basename="$target.$arch.config"
|
|
local config_product="$CONFIG_OUTDIR/$config_basename"
|
|
local config_files=( )
|
|
|
|
for _conf in "${files[@]}"; do
|
|
config_files+=( "$_conf/default.config" )
|
|
if [[ -e "$_conf/$arch.config" ]]; then
|
|
config_files+=( "$_conf/$arch.config" )
|
|
fi
|
|
done
|
|
|
|
$_wrapper_cb "$arch" "$config_product" "${config_files[@]}"
|
|
done
|
|
}
|
|
|
|
for_each_config_target _wrapper "$@"
|
|
}
|
|
|
|
# Simply concat the backing config files in order into a single files for each config target
|
|
populate_configs () {
|
|
_merge_config () {
|
|
local target=$1; shift
|
|
local config_basename output_config
|
|
for arch in "${CONFIG_ARCH[@]}"; do
|
|
config_basename="$target.$arch.config"
|
|
output_config="$CONFIG_OUTDIR/$config_basename"
|
|
|
|
echo "Populating $config_basename from base configs..."
|
|
|
|
for conf in "$@"; do
|
|
cat "$conf/default.config"
|
|
if [[ -e "$conf/$arch.config" ]]; then
|
|
cat "$conf/$arch.config"
|
|
fi
|
|
done | config_sanitizer > "$output_config"
|
|
done
|
|
}
|
|
|
|
for_each_config_target _merge_config "$@"
|
|
}
|
|
|
|
makedef_configs () {
|
|
_makedef_config() {
|
|
local target=$1; shift
|
|
local populated_config
|
|
|
|
for arch in "${CONFIG_ARCH[@]}"; do
|
|
populated_config="$CONFIG_OUTDIR/$target.$arch.config"
|
|
|
|
# config base name is always in this format: <name>.<arch>.config
|
|
echo "Processing $(basename "$populated_config") with make olddefconfig..."
|
|
|
|
if ! [ -f "$populated_config" ]; then
|
|
error "Config not found: '$populated_config'"
|
|
continue
|
|
fi
|
|
|
|
pushd "$TOPDIR" > /dev/null || exit 1
|
|
|
|
config_make "$arch" KCONFIG_CONFIG="$populated_config" olddefconfig
|
|
|
|
popd > /dev/null || return
|
|
done
|
|
}
|
|
|
|
for_each_config_target _makedef_config "$@"
|
|
}
|
|
|
|
sanity_check_configs () {
|
|
# We use LOCALVERSION in Kconfig for variant, save a make flag, but require more sanity check to avoid misuse.
|
|
# First ensure all arch have the same value
|
|
# Then ensure it's in a acceptable format
|
|
_sanity_check_configs() {
|
|
local target=$1; shift
|
|
local populated_config
|
|
local localversion arch_localversion auto_localversion conf_localversion
|
|
|
|
for arch in "${CONFIG_ARCH[@]}"; do
|
|
populated_config="$CONFIG_OUTDIR/$target.$arch.config"
|
|
|
|
# config base name is always in this format: <name>.<arch>.config
|
|
echo "Checking $(basename "$populated_config")..."
|
|
|
|
if ! [ -f "$populated_config" ]; then
|
|
error "Config not populated: '$populated_config'"
|
|
error "sanity_check_configs need to be called after the configs are populated"
|
|
exit 1
|
|
fi
|
|
|
|
auto_localversion=$(sed -ne 's/^CONFIG_LOCALVERSION_AUTO=\(.*\)$/\1/pg' "$populated_config")
|
|
if [ -n "$auto_localversion" ] && [ "$auto_localversion" != "n" ]; then
|
|
error "CONFIG_LOCALVERSION_AUTO must be unset, but it's set in config target $target"
|
|
error "This will break dist build system's release versioning."
|
|
exit 1
|
|
fi
|
|
|
|
conf_localversion=$(sed -ne 's/^CONFIG_LOCALVERSION=\(.*\)$/\1/pg' "$populated_config")
|
|
conf_localversion=${conf_localversion##\"}
|
|
conf_localversion=${conf_localversion%%\"}
|
|
if [ 1 -lt "$(echo "$conf_localversion" | wc -l)" ]; then
|
|
error "More than one LOCALVERSION is set for config target '$target'"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$localversion" ]; then
|
|
localversion=$conf_localversion
|
|
else
|
|
arch_localversion=$conf_localversion
|
|
if [ "$arch_localversion" != "$localversion" ]; then
|
|
error "Unexpected '$arch_localversion' != '$localversion':"
|
|
error "This breaks SRPM package naming, LOCALVERSION inconsistent between sub-arches for config target '$target'"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
localversion=${localversion#\"}
|
|
localversion=${localversion%\"}
|
|
|
|
case $localversion in
|
|
+debuginfo|+core|+devel|+headers|+modules|+$KDIST )
|
|
error "Unexpected LOCALVERSION '$localversion':"
|
|
error "LOCALVERSION is using a reserved keyword, this will cause package dependency breakage."
|
|
exit 1
|
|
;;
|
|
+* )
|
|
;;
|
|
'' )
|
|
# Empty value is default and fine
|
|
;;
|
|
*)
|
|
error "Unexpected LOCALVERSION '$localversion':"
|
|
error "LOCALVERSION is not in acceptable format, for dist building system, it need to start with a '+'"
|
|
error "to distinguish it from release string, and dist build system also need to manipulate the string based on above fact."
|
|
exit 1
|
|
esac
|
|
}
|
|
|
|
for_each_config_target _sanity_check_configs "$@"
|
|
}
|