This commit is contained in:
Krzesimir Nowak 2023-06-09 10:15:28 +02:00
parent d34ab03f21
commit f9948bb8f7
16 changed files with 6186 additions and 0 deletions

2
pkg_auto/Makefile Normal file
View File

@ -0,0 +1,2 @@
shellcheck:
docker run --rm -v "$$PWD:/mnt" koalaman/shellcheck:latest --norc --shell=bash --source-path=SCRIPTDIR --source-path=SCRIPTDIR/impl --external-sources --check-sourced *.sh impl/*.sh

47
pkg_auto/config_template Normal file
View File

@ -0,0 +1,47 @@
# Path to the toplevel directory of the scripts repo.
scripts: ..
# Path to the directory with auxiliary files.
aux: ../../downloads
# Path to the directory where update reports will be stored.
reports: ../../reports
# Base scripts branch for old state.
#
# Old state serves as an state before the updates.
#
# Optional, defaults to origin/main
#old-base: origin/main
# Base scripts branch for new state.
#
# New state serves as an state that's either after the updates or to-be-updated.
#
# Optional, defaults to old-base value
#new-base: origin/main
# Cleanups mode with semicolon-separated list of extra arguments.
#
# Can be:
#
# file,${path_to_file}
# stores cleanup steps in the file. Can be later sourced to perform cleanups.
#
# trap
# executes cleanups on script exit
#
# ignore
# performs no cleanups at all
#
# Optional, if not specified, defaults to ignore.
#cleanups: file,../../x-cleanups
#cleanups: trap
#cleanups: ignore
# Override image name to use for an SDK container.
#
# Optional, defaults to
# flatcar-packages-${arch}:${last_nightly_version_id_in_base}-${last_nightly_build_id_in_base}
#amd64-sdk-img=
#arm64-sdk-img=

View File

@ -0,0 +1,238 @@
#!/bin/bash
##
## Downloads package SDKs from bincache and loads them with
## docker. Downloads package listings from bincache. Version can be
## taken either from the latest nightly tag in the passed scripts
## directory (with the -s option) or from specified version ID and
## build ID (with -v and -b options). The results are written to the
## passed downloads directory.
##
## Parameters:
## -b <ID>: build ID, conflicts with -s
## -h: this help
## -s <DIR>: scripts repo directory, conflicts with -v and -b
## -v <ID>: version ID, conflicts with -s
## -nd: skip downloading of docker images
## -nl: skip downloading of listings
## -x <FILE>: cleanup file
##
## Positional:
## 1: downloads directory
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/cleanups.sh"
CLEANUP_FILE=
SCRIPTS=
VERSION_ID=
BUILD_ID=
SKIP_DOCKER=
SKIP_LISTINGS=
while [[ ${#} -gt 0 ]]; do
case ${1} in
-b)
if [[ -n ${SCRIPTS} ]]; then
fail '-b cannot be used at the same time with -s'
fi
if [[ -z ${2:-} ]]; then
fail 'missing value for -b'
fi
BUILD_ID=${2}
shift 2
;;
-h)
print_help
exit 0
;;
-s)
if [[ -n ${VERSION_ID} ]] || [[ -n ${BUILD_ID} ]]; then
fail '-s cannot be used at the same time with -v or -b'
fi
if [[ -z ${2:-} ]]; then
fail 'missing value for -s'
fi
SCRIPTS=${2}
shift 2
;;
-v)
if [[ -n ${SCRIPTS} ]]; then
fail '-v cannot be used at the same time with -s'
fi
if [[ -z ${2:-} ]]; then
fail 'missing value for -v'
fi
VERSION_ID=${2}
shift 2
;;
-x)
if [[ -z ${2:-} ]]; then
fail 'missing value for -x'
fi
CLEANUP_FILE=${2}
shift 2
;;
-nd)
SKIP_DOCKER=x
shift
;;
-nl)
SKIP_LISTINGS=x
shift
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ ${#} -ne 1 ]]; then
fail 'Expected one positional parameter: a downloads directory'
fi
DOWNLOADS_DIR=$(realpath "${1}"); shift
if [[ -z ${SCRIPTS} ]] && [[ -z ${VERSION_ID} ]]; then
fail 'need to pass either -s or -v (latter with the optional -b too)'
fi
if [[ -n ${CLEANUP_FILE} ]]; then
dirname_out "${CLEANUP_FILE}" cleanup_dir
# shellcheck disable=SC2154 # cleanup_dir is assigned in dirname_out
mkdir -p "${cleanup_dir}"
unset cleanup_dir
setup_cleanups file "${CLEANUP_FILE}"
else
setup_cleanups ignore
fi
if [[ ! -d "${DOWNLOADS_DIR}" ]]; then
add_cleanup "rmdir ${DOWNLOADS_DIR@Q}"
mkdir "${DOWNLOADS_DIR}"
fi
function download() {
local url output
url="${1}"; shift
output="${1}"; shift
info "Downloading ${url}"
curl \
--fail \
--show-error \
--location \
--retry-delay 1 \
--retry 60 \
--retry-connrefused \
--retry-max-time 60 \
--connect-timeout 20 \
"${url}" >"${output}"
}
if [[ -n ${SCRIPTS} ]]; then
# shellcheck disable=SC1091 # sourcing generated file
VERSION_ID=$(source "${SCRIPTS}/sdk_container/.repo/manifests/version.txt"; printf '%s' "${FLATCAR_VERSION_ID}")
# shellcheck disable=SC1091 # sourcing generated file
BUILD_ID=$(source "${SCRIPTS}/sdk_container/.repo/manifests/version.txt"; printf '%s' "${FLATCAR_BUILD_ID}")
fi
ver_plus="${VERSION_ID}${BUILD_ID:++}${BUILD_ID}"
ver_dash="${VERSION_ID}${BUILD_ID:+-}${BUILD_ID}"
exts=(zst bz2 gz)
# shellcheck disable=SC2034 # used indirectly as cmds_name and cmds
zst_cmds=(
zstd
)
# shellcheck disable=SC2034 # used indirectly as cmds_name and cmds
bz2_cmds=(
lbunzip2
pbunzip2
bunzip2
)
# shellcheck disable=SC2034 # used indirectly as cmds_name and cmds
gz_cmds=(
unpigz
gunzip
)
for arch in amd64 arm64; do
if [[ -z ${SKIP_DOCKER} ]]; then
packages_image_name="flatcar-packages-${arch}:${ver_dash}"
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -q -x -F "${packages_image_name}"; then
info "No ${packages_image_name} available in docker, pulling it from bincache"
for ext in "${exts[@]}"; do
tb="${DOWNLOADS_DIR}/packages-sdk-${arch}.tar.${ext}"
if [[ -s ${tb} ]]; then
break;
else
add_cleanup "rm -f ${tb@Q}"
if download "https://bincache.flatcar-linux.net/containers/${ver_dash}/flatcar-packages-${arch}-${ver_dash}.tar.${ext}" "${tb}"; then
break
fi
fi
done
info "Loading ${packages_image_name} into docker"
cmds_name="${ext}_cmds"
if ! declare -p "${cmds_name}" >/dev/null 2>/dev/null; then
fail "Failed to extract ${tb@Q} - no tools to extract ${ext@Q} files"
fi
declare -n cmds="${ext}_cmds"
loaded=
for cmd in "${cmds[@]}"; do
if ! command -v "${cmd}" >/dev/null; then
info "${cmd@Q} is not available"
continue
fi
info "Using ${cmd@Q} to extract the tarball"
"${cmd}" -d -c "${tb}" | docker load
add_cleanup "docker rmi ${packages_image_name@Q}"
loaded=x
break
done
if [[ -z ${loaded} ]]; then
fail "Failed to extract ${tb@Q} - no known available tool to extract it"
fi
unset -n cmds
fi
fi
if [[ -z ${SKIP_LISTINGS} ]]; then
listing_dir="${DOWNLOADS_DIR}/${arch}"
add_cleanup "rmdir ${listing_dir@Q}"
mkdir "${listing_dir}"
base_url="https://bincache.flatcar-linux.net/images/${arch}/${ver_plus}"
for infix in '' 'rootfs-included-sysexts'; do
index_html="${listing_dir}/${infix}${infix:+-}index.html"
url="${base_url}${infix:+/}${infix}"
add_cleanup "rm -f ${index_html@Q}"
download "${url}/" "${index_html}"
# get names of all files ending with _packages.txt
mapfile -t listing_files < <(grep -F '_packages.txt"' "${index_html}" | sed -e 's#.*"\(\./\)\?\([^"]*\)".*#\2#')
for listing in "${listing_files[@]}"; do
info "Downloading ${listing} for ${arch}"
listing_path="${listing_dir}/${listing}"
add_cleanup "rm -f ${listing_path@Q}"
download "${url}/${listing}" "${listing_path}"
done
done
fi
done
info 'Done'

148
pkg_auto/generate_config.sh Executable file
View File

@ -0,0 +1,148 @@
#!/bin/bash
set -euo pipefail
##
## Generate a config.
##
## Parameters:
## -a: aux directory
## -d: debug package - list many times
## -h: this help
## -i: SDK image override in form of ${arch}:${name},
## -n: new base
## -o: old base
## -r: reports directory
## -s: scripts directory
## -x: cleanup opts
##
## Positional:
## 1: path for config file
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/cleanups.sh"
# shellcheck disable=SC2034 # used by name below
gc_aux_directory=''
# shellcheck disable=SC2034 # used by name below
gc_new_base=''
# shellcheck disable=SC2034 # used by name below
gc_old_base=''
# shellcheck disable=SC2034 # used by name below
gc_reports_directory=''
# shellcheck disable=SC2034 # used by name below
gc_scripts_directory=''
# shellcheck disable=SC2034 # used by name below
gc_cleanup_opts=''
# gc_${arch}_sdk_img are declared on demand
gc_debug_packages=()
declare -A opt_map
opt_map=(
['-a']=gc_aux_directory
['-n']=gc_new_base
['-o']=gc_old_base
['-r']=gc_reports_directory
['-s']=gc_scripts_directory
['-x']=gc_cleanup_opts
)
while [[ ${#} -gt 0 ]]; do
case ${1} in
-d)
if [[ -z ${2:-} ]]; then
fail 'missing value for -d'
fi
gc_debug_packages+=( "${2}" )
shift 2
;;
-h)
print_help
exit 0
;;
-i)
if [[ -z ${2:-} ]]; then
fail 'missing value for -i'
fi
arch=${2%%:*}
image_name=${2#*:}
var_name="gc_${arch}_sdk_img"
unset arch
# shellcheck disable=SC2178 # shellcheck does not grok refs
declare -n ref="${var_name}"
unset var_name
# shellcheck disable=SC2178 # shellcheck does not grok refs
ref=${image_name}
unset image_name
unset -n ref
shift 2
;;
--)
shift
break
;;
-*)
var_name=${opt_map["${1}"]:-}
if [[ -z ${var_name} ]]; then
fail "unknown flag '${1}'"
fi
if [[ -z ${2:-} ]]; then
fail 'missing value for -w'
fi
# shellcheck disable=SC2178 # shellcheck does not grok refs
declare -n ref="${var_name}"
# shellcheck disable=SC2178 # shellcheck does not grok refs
ref=${2}
unset -n ref
unset var_name
shift 2
;;
*)
break
;;
esac
done
join_by gc_debug_packages_csv ',' "${gc_debug_packages[@]}"
declare -a pairs
pairs=(
'scripts' gc_scripts_directory
'aux' gc_aux_directory
'reports' gc_reports_directory
'old-base' gc_old_base
'new-base' gc_new_base
'cleanups' gc_cleanup_opts
'amd64-sdk-img' gc_amd64_sdk_img
'arm64-sdk-img' gc_arm64_sdk_img
'debug-packages' gc_debug_packages_csv
)
if [[ ${#} -ne 1 ]]; then
fail 'expected one positional parameters: a path for the config'
fi
config=${1}; shift
{
opt_idx=0
name_idx=1
while [[ ${name_idx} -lt "${#pairs[@]}" ]]; do
opt=${pairs["${opt_idx}"]}
name=${pairs["${name_idx}"]}
opt_idx=$((opt_idx + 2))
name_idx=$((name_idx + 2))
# shellcheck disable=SC2178 # shellcheck does not grok refs
declare -n ref="${name}"
if [[ -n ${ref:-} ]]; then
printf '%s: %s\n' "${opt}" "${ref}"
fi
unset -n ref
done
unset opt_idx name_idx
} >"${config}"
info 'Done, but note that the config is not guaranteed to be valid!'

56
pkg_auto/generate_reports.sh Executable file
View File

@ -0,0 +1,56 @@
#!/bin/bash
set -euo pipefail
##
## Generates reports.
##
## Parameters:
## -w: path to use for workdir
## -h: this help
##
## Positional:
## 1: config file
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/pkg_auto_lib.sh"
workdir=''
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h)
print_help
exit 0
;;
-w)
if [[ -z ${2:-} ]]; then
fail 'missing value for -w'
fi
workdir=${2}
shift 2
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ ${#} -ne 1 ]]; then
fail 'expected one positional parameter: a config file'
fi
config_file=${1}; shift
setup_workdir_with_config "${workdir}" "${config_file}"
generate_package_update_reports

153
pkg_auto/impl/cleanups.sh Normal file
View File

@ -0,0 +1,153 @@
#!/bin/bash
#
# Cleanups
#
# This is basically a command stack to be executed in some deferred
# time. So last command added will be the first to be executed at
# cleanup time.
#
# Cleanups are implemented through two functions, setup_cleanups and
# add_cleanup, prefixed with _${type}_. So for type "foo" the
# functions would be _foo_setup_cleanups and _foo_add_cleanup.
#
# setup_cleanup may take some extra parameters that are specific to
# the type. For example file type takes a path where the commands will
# be stored.
#
# add_cleanup takes one or more command to add to the cleanup stack.
#
if [[ -z ${__CLEANUPS_SH_INCLUDED__:-} ]]; then
__CLEANUPS_SH_INCLUDED__=x
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
# Sets up cleanup stack of a given type. A type may need some extra
# parameter, which comes after a comma. Possible types are:
#
# - file: requires extra argument about cleanup file location; an
# example could be "file,/path/to/cleanups-file"
# - trap: executed on shell exit
# - ignore: noop
#
# Params:
#
# 1 - type of cleanup
function setup_cleanups() {
local kind
kind=${1}; shift
if [[ -n ${_cleanups_sh_cleanup_kind_:-} ]]; then
fail "cannot set cleanups to '${kind}', they are already set up to '${_cleanups_sh_cleanup_kind_}'"
fi
declare -g _cleanups_sh_cleanup_kind_
_ensure_valid_cleanups_sh_cleanup_kind_ "${kind}"
_cleanups_sh_cleanup_kind_=${kind}
_call_cleanup_func setup_cleanups "${@}"
}
# Adds commands to the cleanup stack.
#
# Params:
#
# @ - commands, one per parameter
function add_cleanup() {
_call_cleanup_func add_cleanup "${@}"
}
#
# Implementation details.
#
# "file" cleanups
function _file_setup_cleanups() {
if [[ ${#} -ne 1 ]]; then
fail 'missing cleanup file location argument for file cleanups'
fi
declare -g _file_cleanup_file
_file_cleanup_file=$(realpath "${1}"); shift
add_cleanup "rm -f ${_file_cleanup_file@Q}"
}
function _file_add_cleanup() {
local fac_cleanup_dir tmpfile
dirname_out "${_file_cleanup_file}" fac_cleanup_dir
tmpfile=$(mktemp -p "${fac_cleanup_dir}")
printf '%s\n' "${@}" >"${tmpfile}"
if [[ -f "${_file_cleanup_file}" ]]; then
cat "${_file_cleanup_file}" >>"${tmpfile}"
fi
mv -f "${tmpfile}" "${_file_cleanup_file}"
}
# "trap" cleanups
function _trap_update_trap() {
# shellcheck disable=SC2064 # using double quotes on purpose instead of single quotes
trap "${_trap_cleanup_actions}" EXIT
}
function _trap_setup_cleanups() {
declare -g _trap_cleanup_actions
_trap_cleanup_actions=':'
declare -g -A _trap_cleanup_snapshots
_trap_cleanup_snapshots=()
_trap_update_trap
}
function _trap_add_cleanup() {
local tac_joined
join_by tac_joined ' ; ' "${@/%/' || :'}"
_trap_cleanup_actions="${tac_joined} ; ${_trap_cleanup_actions}"
_trap_update_trap
}
# "ignore" cleanups
function _ignore_setup_cleanups() {
:
}
function _ignore_add_cleanup() {
:
}
function _ensure_valid_cleanups_sh_cleanup_kind_() {
local kind
kind=${1}; shift
local -a functions=(
setup_cleanups
add_cleanup
)
local func
for func in "${functions[@]/#/_${kind}_}"; do
if ! declare -pF "${func}" >/dev/null 2>/dev/null; then
fail "kind '${kind}' is not a valid cleanup kind, function '${func}' is not defined"
fi
done
}
function _call_cleanup_func() {
local func_name
func_name=${1}; shift
if [[ -z "${_cleanups_sh_cleanup_kind_}" ]]; then
_cleanups_sh_cleanup_kind_='trap'
fi
local func
func="_${_cleanups_sh_cleanup_kind_}_${func_name}"
"${func}" "${@}"
}
fi

View File

@ -0,0 +1,485 @@
#!/bin/bash
if [[ -z ${__INSIDE_SDK_CONTAINER_LIB_SH_INCLUDED__:-} ]]; then
__INSIDE_SDK_CONTAINER_LIB_SH_INCLUDED__=x
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
# Invokes emerge to get a report about built packages for a given
# metapackage in the given root that has a portage configuration.
#
# Params:
#
# 1 - root filesystem with the portage config
# 2 - metapackage to get the deps from
function emerge_pretend() {
local root package
root=${1}; shift
package=${1}; shift
# Probably a bunch of those flags are not necessary, but I'm not
# touching it - they seem to be working. :)
local -a emerge_opts=(
--config-root="${root}"
--root="${root}"
--sysroot="${root}"
--pretend
--columns
--nospinner
--oneshot
--color n
--emptytree
--verbose
--verbose-conflicts
--verbose-slot-rebuilds y
--changed-deps y
--changed-deps-report y
--changed-slot y
--changed-use
--newuse
--complete-graph y
--deep
--rebuild-if-new-slot y
--rebuild-if-unbuilt y
--with-bdeps y
--dynamic-deps y
--update
--ignore-built-slot-operator-deps y
--selective n
--keep-going y
)
local rv
rv=0
emerge "${emerge_opts[@]}" "${package}" || rv=${?}
if [[ ${rv} -ne 0 ]]; then
echo "WARNING: emerge exited with status ${rv}" >&2
fi
}
# Gets package list for SDK.
function package_info_for_sdk() {
local root
root='/'
ignore_crossdev_stuff "${root}"
emerge_pretend "${root}" coreos-devel/sdk-depends
revert_crossdev_stuff "${root}"
}
# Gets package list for board of a given architecture.
#
# Params:
#
# 1 - architecture
function package_info_for_board() {
local arch
arch=${1}; shift
local root
root="/build/${arch}-usr"
# Ignore crossdev stuff in both SDK root and board root - emerge
# may query SDK stuff for the board packages.
ignore_crossdev_stuff /
ignore_crossdev_stuff "${root}"
emerge_pretend "${root}" coreos-devel/board-packages
revert_crossdev_stuff "${root}"
revert_crossdev_stuff /
}
# Set the directory where the emerge output and the results of
# processing it will be stored. EO stands for "emerge output"
#
# Params:
#
# 1 - directory path
function set_eo() {
local dir=${1}; shift
SDK_EO="${dir}/sdk-emerge-output"
BOARD_EO="${dir}/board-emerge-output"
# shellcheck disable=SC2034 # used indirectly in cat_eo_f
SDK_EO_F="${SDK_EO}-filtered"
# shellcheck disable=SC2034 # used indirectly in cat_eo_f
BOARD_EO_F="${BOARD_EO}-filtered"
# shellcheck disable=SC2034 # used indirectly in cat_eo_w
SDK_EO_W="${SDK_EO}-warnings"
# shellcheck disable=SC2034 # used indirectly in cat_eo_w
BOARD_EO_W="${BOARD_EO}-warnings"
}
# Print the contents of file, path of which is stored in a variable of
# a given name.
#
# Params:
#
# 1 - name of the variable
function cat_var() {
local var_name
var_name=${1}; shift
local -n ref="${var_name}"
if [[ -z "${ref+isset}" ]]; then
fail "${var_name} unset"
fi
if [[ ! -e "${ref}" ]]; then
fail "${ref} does not exist"
fi
cat "${ref}"
}
# Print contents of the emerge output of a given kind. Kind can be
# either either sdk or board.
#
# Params:
#
# 1 - kind
function cat_eo() {
local kind
kind=${1}; shift
cat_var "${kind^^}_EO"
}
# Print contents of the filtered emerge output of a given kind. Kind
# can be either either sdk or board. The filtered emerge output
# contains only lines with package information.
#
# Params:
#
# 1 - kind
function cat_eo_f() {
local kind
kind=${1}; shift
cat_var "${kind^^}_EO_F"
}
# Print contents of a file that stores warnings that were printed by
# emerge. The warnings are specific to a kind (sdk or board).
#
# Params:
#
# 1 - kind
function cat_eo_w() {
local kind
kind=${1}; shift
cat_var "${kind^^}_EO_W"
}
# JSON output would be more verbose, but probably would not require
# these abominations below. But, alas, emerge doesn't have that yet.
# status package name version slot repo target (opt) keyvals size
# |--------------| |----------| |#-g1-----------#--#-g2-#| |--|-g------| |-g----------#-#-g-----| |---|
# [ebuild R ~] virtual/rust [1.71.1:0/llvm-16::coreos] to /some/root USE="-rustfmt" FOO="bar" 0 KiB
#
# Actually, there can also be another "version slot repo" part between
# the first "version slot repo" and "target" part.
STATUS_RE='\[[^]]*]' # 0 groups
PACKAGE_NAME_RE='[^[:space:]]*' # 0 groups
VER_SLOT_REPO_RE='\[\([^]]\+\)::\([^]]\+\)]' # 2 groups
TARGET_RE='to[[:space:]]\+\([^[:space:]]\)\+' # 1 group
KEYVALS_RE='\([[:space:]]*[A-Za-z0-9_]*="[^"]*"\)*' # 1 group (but containing only the last pair!)
SIZE_RE='[[:digit:]]\+[[:space:]]*[[:alpha:]]*B' # 0 groups
SPACES_RE='[[:space:]]\+' # 0 groups
NONSPACES_RE='[^[:space:]]\+' # 0 groups
NONSPACES_WITH_COLON_RE='[^[:space:]]*:' # 0 groups
FULL_LINE_RE='^'"${STATUS_RE}${SPACES_RE}${PACKAGE_NAME_RE}"'\('"${SPACES_RE}${VER_SLOT_REPO_RE}"'\)\{1,2\}\('"${SPACES_RE}${TARGET_RE}"'\)\?\('"${SPACES_RE}${KEYVALS_RE}"'\)*'"${SPACES_RE}${SIZE_RE}"'$'
# Filters sdk reports to get the package information.
function filter_sdk_eo() {
cat_eo sdk | xgrep -e "${FULL_LINE_RE}"
}
# Filters board reports for a given arch to get the package
# information.
#
# Params:
#
# 1 - architecture
function filter_board_eo() {
local arch
arch=${1}; shift
# Replace ${arch}-usr in the output with a generic word BOARD.
cat_eo board | \
xgrep -e "${FULL_LINE_RE}" | \
sed -e "s#/build/${arch}-usr/#/build/BOARD/#"
}
# Filters sdk reports to get anything but the package information
# (i.e. junk).
function junk_sdk_eo() {
cat_eo sdk | xgrep -v -e "${FULL_LINE_RE}"
}
# Filters board reports to get anything but the package information
# (i.e. junk).
function junk_board_eo() {
cat_eo board | xgrep -v -e "${FULL_LINE_RE}"
}
# More regexp-like abominations follow.
# There may also be a line like:
#
# [blocks B ] <dev-util/gdbus-codegen-2.76.4 ("<dev-util/gdbus-codegen-2.76.4" is soft blocking dev-libs/glib-2.76.4)
#
# But currently we don't care about those - they land in junk.
SLOT_INFO_SED_FILTERS=(
# if there is no slot information in version, add :0
#
# assumption here is that version is a second word
-e "/^${NONSPACES_RE}${SPACES_RE}${NONSPACES_WITH_COLON_RE}/ ! s/^\(${NONSPACES_RE}${SPACES_RE}${NONSPACES_RE}\)/\1:0/"
)
PKG_VER_SLOT_SED_FILTERS=(
# from line like:
#
# [ebuild R ~] virtual/rust [1.71.1:0/llvm-16::coreos] USE="-rustfmt" 0 KiB
#
# extract package name, version and optionally a slot if it exists, the result would be:
#
# virtual/rust 1.71.1:0/llvm-16
-e "s/^${STATUS_RE}${SPACES_RE}\(${PACKAGE_NAME_RE}\)${SPACES_RE}${VER_SLOT_REPO_RE}.*/\1 \2/"
"${SLOT_INFO_SED_FILTERS[@]}"
)
PKG_VER_SLOT_KV_SED_FILTERS=(
# from line like:
#
# [ebuild R ~] virtual/rust [1.71.1:0/llvm-16::coreos] USE="-rustfmt" 0 KiB
#
# extract package name, version, optionally a slot if it exists and key value pairs if any, the result would be:
#
# virtual/rust 1.71.1:0/llvm-16 USE="-rustfmt"
-e "s/${STATUS_RE}${SPACES_RE}\(${PACKAGE_NAME_RE}\)${SPACES_RE}${VER_SLOT_REPO_RE}\(${SPACES_RE}${VER_SLOT_REPO_RE}\)\?\(${SPACES_RE}${TARGET_RE}\)\?${SPACES_RE}\(${KEYVALS_RE}\)${SPACES_RE}${SIZE_RE}\$/\1 \2 \9/"
"${SLOT_INFO_SED_FILTERS[@]}"
)
PKG_REPO_SED_FILTERS=(
# from line like:
#
# [ebuild R ~] virtual/rust [1.71.1:0/llvm-16::coreos] USE="-rustfmt" 0 KiB
#
# extract package name and repo, the result would be:
#
# virtual/rust coreos
-e "s/^${STATUS_RE}${SPACES_RE}\(${PACKAGE_NAME_RE}\)${SPACES_RE}${VER_SLOT_REPO_RE}${SPACES_RE}.*/\1 \3/"
)
# Applies some sed filter over the emerge output of a given kind.
# Results are printed.
#
# Params:
#
# 1 - kind (sdk or board)
# @ - parameters passed to sed
function sed_eo_and_sort() {
local kind
kind=${1}; shift
# rest goes to sed
cat_eo_f "${kind}" | sed "${@}" | sort
}
# Applies some sed filter over the SDK emerge output. Results are
# printed.
#
# Params:
#
# @ - parameters passed to sed
function packages_for_sdk() {
# args are passed to sed_eo_and_sort
sed_eo_and_sort sdk "${@}"
}
# Applies some sed filter over the board emerge output. Results are
# printed.
#
# Params:
#
# @ - parameters passed to sed
function packages_for_board() {
# args are passed to sed_eo_and_sort
sed_eo_and_sort board "${@}"
}
# Prints package name, slot and version information for SDK.
function versions_sdk() {
local -a sed_opts
sed_opts=(
"${PKG_VER_SLOT_SED_FILTERS[@]}"
)
packages_for_sdk "${sed_opts[@]}"
}
# Prints package name, slot, version and key-values information for
# SDK. Key-values may be something like USE="foo bar -baz".
function versions_sdk_with_key_values() {
local -a sed_opts
sed_opts=(
"${PKG_VER_SLOT_KV_SED_FILTERS[@]}"
)
packages_for_sdk "${sed_opts[@]}"
}
# Prints package name, slot and version information for board.
function versions_board() {
local -a sed_opts
sed_opts=(
-e '/to \/build\/BOARD\// ! d'
"${PKG_VER_SLOT_SED_FILTERS[@]}"
)
packages_for_board "${sed_opts[@]}"
}
# Prints package name, slot, version and key-values information for
# build dependencies of board. Key-values may be something like
# USE="foo bar -baz".
function board_bdeps() {
local -a sed_opts
sed_opts=(
-e '/to \/build\/BOARD\// d'
"${PKG_VER_SLOT_KV_SED_FILTERS[@]}"
)
packages_for_board "${sed_opts[@]}"
}
# Print package name and source repository names information for SDK.
function package_sources_sdk() {
local -a sed_opts
sed_opts=(
"${PKG_REPO_SED_FILTERS[@]}"
)
packages_for_sdk "${sed_opts[@]}"
}
# Print package name and source repository names information for
# board.
function package_sources_board() {
local -a sed_opts
sed_opts=(
"${PKG_REPO_SED_FILTERS[@]}"
)
packages_for_board "${sed_opts[@]}"
}
# Checks if no errors were produced by emerge when generating
# reports. It is assumed that emerge will print a line with "ERROR" in
# it to denote a failure.
function ensure_no_errors() {
local kind
for kind in sdk board; do
if cat_eo_w "${kind}" | grep --quiet --fixed-strings 'ERROR'; then
fail "there are errors in emerge output warnings files"
fi
done
}
# Stores a path to a package.provided file inside the given root
# filesystem portage configuration. Mostly used to ignore
# cross-toolchains.
#
# Params:
#
# 1 - path to root filesystem with the portage configuration
# 2 - name of a variable where the path will be stored
function get_provided_file() {
local root path_var_name
root=${1}; shift
path_var_name=${1}; shift
local -n path_ref="${path_var_name}"
# shellcheck disable=SC2034 # reference to external variable
path_ref="${root}/etc/portage/profile/package.provided/ignore_cross_packages"
}
# Marks packages coming from crossdev repo as provided at a very high
# version. We do this, because updating their native counterparts will
# cause emerge to complain that cross-<triplet>/<package> is masked
# (like for sys-libs/glibc and cross-x86_64-cros-linux-gnu/glibc),
# because it has no keywords. In theory, we could try updating
# <ROOT>/etc/portage/package.mask/cross-<triplet> file created by the
# crossdev tool to unmask the new version, but it's an unnecessary
# hassle - native and cross package are supposed to be the same ebuild
# anyway, so update information about cross package is redundant.
#
# Params:
#
# 1 - root directory
# 2 - ID of the crossdev repository (optional, defaults to x-crossdev)
function ignore_crossdev_stuff() {
local root crossdev_repo_id
root=${1}; shift
crossdev_repo_id=${1:-x-crossdev}; shift || :
local crossdev_repo_path
crossdev_repo_path=$(portageq get_repo_path "${root}" "${crossdev_repo_id}")
local ics_path ics_dir
get_provided_file "${root}" ics_path
dirname_out "${ics_path}" ics_dir
sudo mkdir -p "${ics_dir}"
env --chdir="${crossdev_repo_path}" find . -type l | \
cut -d/ -f2-3 | \
sed -e 's/$/-9999/' | \
sudo tee "${ics_path}" >/dev/null
}
# Reverts effects of the ignore_crossdev_stuff function.
#
# Params:
#
# 1 - root directory
function revert_crossdev_stuff() {
local root
root=${1}; shift
local ics_path ics_dir
get_provided_file "${root}" ics_path
dirname_out "${ics_path}" ics_dir
sudo rm -f "${ics_path}"
if dir_is_empty "${ics_dir}"; then
sudo rmdir "${ics_dir}"
fi
}
# Checks if the expected reports were generated by emerge.
function ensure_valid_reports() {
local kind var_name
for kind in sdk board; do
var_name="${kind^^}_EO_F"
if [[ ! -s ${!var_name} ]]; then
fail "report files are missing or are empty"
fi
done
}
# Drops the empty warning files in given directory.
#
# Params:
#
# 1 - path to the directory
function clean_empty_warning_files() {
local dir
dir=${1}; shift
local file
for file in "${dir}/"*'-warnings'; do
if [[ ! -s ${file} ]]; then
rm -f "${file}"
fi
done
}
fi

573
pkg_auto/impl/mvm.sh Normal file
View File

@ -0,0 +1,573 @@
#!/bin/bash
#
# "mvm" stands for "multi-valued map", so these are maps of scalars
# (strings, numbers) to other container (arrays or maps)
#
# mvm is implemented with a map that contains some predefined keys,
# like "name", "constructor", "storage", etc.
#
# The "storage" field is the actual "map" part of the "mvm", at the
# values stored in it are names of the global variables being the
# "multi-valued" part of the "mvm". In the code these variables are
# referred to as "mvc" meaning "multi-value container".
#
# The "constructor" and "destructor" fields are here to properly
# implement creating and destroying mvcs. The "adder" field is for
# adding elements to an mvc.
#
# There is also a "counter" field which, together with the "name"
# field, is used for creating the names for mvc variables.
#
# The "extras" field is for user-defined mapping. The mvm will clear
# the mapping itself, but if the values are anything else than simple
# scalars (e.g. names of variables) then the cleanup of those is
# user's task.
#
# There is also an optional field named "iteration_helper" which is a
# callback invoked when iterating over the mvm.
#
# In order to implement a new mvc type, the following functions need
# to be implemented:
#
# <type>_constructor - takes an mvc name; should create an mvc with the
# passed name.
# <type>_destructor - takes an mvc name; should unset an mvc with the
# passed name, should likely take care of cleaning
# up the values stored in the mvc
# <type>_adder - takes an mvc name and values to be added; should add
# the values to the mvc
# <type>_iteration_helper - optional; takes a key, an mvc name, a
# callback and extra arguments to be
# forwarded to the callback; should invoke
# the callback with the extra arguments, the
# key, the mvc name and optionally some
# extra arguments the helper deems useful
if [[ -z ${__MVM_SH_INCLUDED__:-} ]]; then
__MVM_SH_INCLUDED__=x
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
# Used for creating unique names for extras and storage maps.
MVM_COUNTER=0
# Array mvm, the default. Provides an iteration helper that sends all
# the array values to the iteration callback.
function mvm_mvc_array_constructor() {
local array_var_name
array_var_name=${1}; shift
declare -g -a "${array_var_name}"
local -n array_ref=${array_var_name}
array_ref=()
}
function mvm_mvc_array_destructor() {
local array_var_name
array_var_name=${1}; shift
unset "${array_var_name}"
}
function mvm_mvc_array_adder() {
local array_var_name
array_var_name=${1}; shift
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n array_ref=${array_var_name}
array_ref+=( "${@}" )
}
# iteration_helper is optional
function mvm_mvc_array_iteration_helper() {
local key array_var_name callback
key=${1}; shift
array_var_name=${1}; shift
callback=${1}; shift
# rest are extra args passed to cb
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n array_ref=${array_var_name}
"${callback}" "${@}" "${key}" "${array_var_name}" "${array_ref[@]}"
}
# Map mvm. When adding elements to the mvc, it is expected that the
# number of items passed will be even. Odd elements will be used as
# keys, even elements will be used as values.
#
# No iteration helper.
function mvm_mvc_map_constructor() {
local map_var_name
map_var_name=${1}; shift
declare -g -A "${map_var_name}"
local -n map_ref=${map_var_name}
map_ref=()
}
function mvm_mvc_map_destructor() {
local map_var_name
map_var_name=${1}; shift
unset "${map_var_name}"
}
function mvm_mvc_map_adder() {
local map_var_name
map_var_name=${1}; shift
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n map_ref=${map_var_name}
while [[ ${#} -gt 1 ]]; do
# shellcheck disable=SC2034 # it's a reference to external variable
map_ref["${1}"]=${2}
shift 2
done
}
# Set mvm. Behaves like array mvm, but all elements in each set are
# unique and the order of elements is not guaranteed to be the same as
# order of insertions.
function mvm_mvc_set_constructor() {
local set_var_name
set_var_name=${1}; shift
declare -g -A "${set_var_name}"
# shellcheck disable=SC2178 # shellcheck does not grok refs
local -n set_ref=${set_var_name}
set_ref=()
}
function mvm_mvc_set_destructor() {
local set_var_name
set_var_name=${1}
unset "${set_var_name}"
}
function mvm_mvc_set_adder() {
local set_var_name
set_var_name=${1}; shift
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n set_ref=${set_var_name}
while [[ ${#} -gt 0 ]]; do
set_ref["${1}"]=x
shift
done
}
# iteration_helper is optional
function mvm_mvc_set_iteration_helper() {
local key map_var_name callback
key=${1}; shift
set_var_name=${1}; shift
callback=${1}; shift
# rest are extra args passed to cb
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n set_ref=${set_var_name}
"${callback}" "${@}" "${key}" "${set_var_name}" "${!set_ref[@]}"
}
# mvm API
# Creates a new mvm with a passed name, optionally type and
# extras. The name must be globally unique. The type is optional. If
# no type is passed, an array mvm will be assumed. Otherwise the type
# must be valid, i.e. it must provide a constructor, a destructor, an
# adder and, optionally, an iteration helper. The built in types are
# "mvm_mvc_array", "mvm_mvc_set" and "mvm_mvc_map". If any extras are
# passed, they must be preceded with a double dash to avoid ambiguity
# between type and a first extras key. Extras are expected to be even
# in count, odd elements will be used as keys, even elements will be
# used as values.
#
# Params:
#
# 1 - name of the mvm
# @ - optional mvc type, optionally followed by double dash and extras
# key-value pairs.
function mvm_declare() {
local mvm_var_name
mvm_var_name=${1}; shift
if declare -p "${mvm_var_name}" >/dev/null 2>/dev/null; then
fail "variable ${mvm_var_name} already exists, declaring mvm for it would clobber it"
fi
local value_handler_prefix
value_handler_prefix=''
if [[ ${#} -gt 0 ]]; then
if [[ ${1} != '--' ]]; then
value_handler_prefix=${1}
shift
fi
if [[ ${#} -gt 0 ]]; then
if [[ ${1} != '--' ]]; then
fail "missing double-dash separator between optional value handler prefix and extra key value pairs for '${mvm_var_name}'"
fi
shift
fi
fi
if [[ -z ${value_handler_prefix} ]]; then
value_handler_prefix=mvm_mvc_array
fi
# rest are key value pairs for extras
mvm_debug "${mvm_var_name}" "using prefix ${value_handler_prefix}"
local constructor destructor adder iteration_helper
constructor="${value_handler_prefix}_constructor"
destructor="${value_handler_prefix}_destructor"
adder="${value_handler_prefix}_adder"
iteration_helper="${value_handler_prefix}_iteration_helper"
local func
for func in "${constructor}" "${destructor}" "${adder}"; do
if ! declare -pF "${func}" >/dev/null 2>/dev/null; then
fail "'${func}' is not a function, is '${value_handler_prefix}' a valid prefix?"
fi
done
if ! declare -pF "${iteration_helper}" >/dev/null 2>/dev/null; then
mvm_debug "${mvm_var_name}" "no interation helper available"
iteration_helper=''
fi
local extras_idx storage_idx extras_map_var_name storage_map_var_name
extras_idx=$((MVM_COUNTER))
storage_idx=$((MVM_COUNTER + 1))
extras_map_var_name="mvm_stuff_${extras_idx}"
storage_map_var_name="mvm_stuff_${storage_idx}"
MVM_COUNTER=$((MVM_COUNTER + 2))
declare -g -A "${mvm_var_name}" "${extras_map_var_name}" "${storage_map_var_name}"
mvm_debug "${mvm_var_name}" "extras map: ${extras_map_var_name}, storage_map: ${storage_map_var_name}"
local -n storage_map_ref=${storage_map_var_name}
storage_map_ref=()
local -n mvm_ref=${mvm_var_name}
# shellcheck disable=SC2034 # it's a reference to external variable
mvm_ref=(
['name']="${mvm_var_name}"
['constructor']="${constructor}"
['destructor']="${destructor}"
['adder']="${adder}"
['iteration_helper']="${iteration_helper}"
['counter']=0
['extras']="${extras_map_var_name}"
['storage']="${storage_map_var_name}"
)
local -n extras_map_ref=${extras_map_var_name}
while [[ ${#} -gt 1 ]]; do
mvm_debug "${mvm_var_name}" "adding ${1} -> ${2} pair to extras"
extras_map_ref["${1}"]=${2}
shift 2
done
if [[ ${#} -gt 0 ]]; then
fail "odd number of parameters for extra key value information for '${mvm_var_name}'"
fi
}
# Takes a name of mvm, a callback, and extra parameters that will be
# forwarded to the callback. Before invoking the callback, the
# function will declare a local variable called "mvm" which is a
# reference to the variable with the passed name. The "mvm" variable
# can be used for easy access to the map within the callback.
#
# The convention is that the function foo_barize will use mvm_call to
# invoke a callback named foo_c_barize. The foo_c_barize function can
# invoke other _c_ infixed functions, like mvm_c_get_extra or
# mvm_c_get.
#
# Params:
#
# 1 - name of mvm variable
# 2 - name of the callback
# @ - arguments for the callback
function mvm_call() {
local name func
name=${1}; shift
func=${1}; shift
# rest are func args
mvm_debug "${name}" "invoking ${func} with args: ${*@Q}"
# The "mvm" variable can be used by ${func} now.
local -n mvm=${name}
"${func}" "${@}"
}
# Internal function that generates a name for mvc based on passed name
# and counter.
function __mvm_mvc_name() {
local name counter mvc_name_var_name
name=${1}; shift
counter=${1}; shift
mvc_name_var_name=${1}; shift
local -n mvc_name_ref=${mvc_name_var_name}
# shellcheck disable=SC2034 # it's a reference to external variable
mvc_name_ref="mvm_${name}_mvc_${counter}"
}
# Destroy the mvm with passed name.
#
# Params:
#
# 1 - name of mvm to destroy
function mvm_unset() {
mvm_call "${1}" mvm_c_unset "${@:2}"
}
# Helper function for mvm_unset invoked through mvm_call.
function mvm_c_unset() {
local counter name extras_map_var_name storage_map_var_name destructor mvm_mcu_mvc_name
counter=${mvm['counter']}
name=${mvm['name']}
extras_map_var_name=${mvm['extras']}
storage_map_var_name=${mvm['storage']}
destructor=${mvm['destructor']}
while [[ ${counter} -gt 0 ]]; do
counter=$((counter - 1))
__mvm_mvc_name "${name}" "${counter}" mvm_mcu_mvc_name
"${destructor}" "${mvm_mcu_mvc_name}"
done
unset "${storage_map_var_name}"
unset "${extras_map_var_name}"
unset "${name}"
}
# Gets an value from extras map for a given key.
#
# Params:
#
# 1 - name of the mvm variable
# 2 - extra key
# 3 - name of a variable where the extra value will be stored
function mvm_get_extra() {
mvm_call "${1}" mvm_c_get_extra "${@:2}"
}
# Helper function for mvm_get_extra invoked through mvm_call.
function mvm_c_get_extra() {
local extra extra_var_name
extra=${1}; shift
extra_var_name=${1}; shift
local -n extra_ref=${extra_var_name}
local extras_map_var_name
extras_map_var_name=${mvm['extras']}
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n extras_map_ref=${extras_map_var_name}
# shellcheck disable=SC2034 # it's a reference to external variable
extra_ref=${extras_map_ref["${extra}"]:-}
}
# Gets a name of the mvc for a given key.
#
# Params:
#
# 1 - name of the mvm variable
# 2 - key
# 3 - name of a variable where the mvc name will be stored
function mvm_get() {
mvm_call "${1}" mvm_c_get "${@:2}"
}
# Helper function for mvm_get invoked through mvm_call.
function mvm_c_get() {
local key value_var_name
key=${1}; shift
value_var_name=${1}; shift
local -n value_ref=${value_var_name}
local storage_map_var_name
storage_map_var_name=${mvm['storage']}
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n storage_map_ref=${storage_map_var_name}
# shellcheck disable=SC2034 # it's a reference to external variable
value_ref=${storage_map_ref["${key}"]:-}
}
# Internal function for creating a new mvc.
function __mvm_c_make_new_mvc() {
local key mvc_name_var_name
key=${1}; shift
mvc_name_var_name=${1}; shift
local name counter storage_map_var_name
name=${mvm['name']}
counter=${mvm['counter']}
storage_map_var_name=${mvm['storage']}
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n storage_map_ref=${storage_map_var_name}
__mvm_mvc_name "${name}" "${counter}" "${mvc_name_var_name}"
local constructor
constructor=${mvm['constructor']}
"${constructor}" "${!mvc_name_var_name}"
mvm['counter']=$((counter + 1))
storage_map_ref["${key}"]=${!mvc_name_var_name}
}
# Adds passed elements to the mvm under the given key. If an mvc for
# the key didn't exist in the mvm, it gets created.
#
# Params:
#
# 1 - name of the mvm variable
# 2 - key
# @ - elements
function mvm_add() {
mvm_call "${1}" mvm_c_add "${@:2}"
}
# Helper function for mvm_add invoked through mvm_call.
function mvm_c_add() {
local key
key=${1}; shift
# rest are values to add
local adder mvm_mca_mvc_name
adder=${mvm['adder']}
mvm_c_get "${key}" mvm_mca_mvc_name
if [[ -z ${mvm_mca_mvc_name} ]]; then
__mvm_c_make_new_mvc "${key}" mvm_mca_mvc_name
fi
"${adder}" "${mvm_mca_mvc_name}" "${@}"
}
# Removes the key from the mvm.
#
# Params:
#
# 1 - name of the mvm variable
# 2 - key
function mvm_remove() {
mvm_call "${1}" mvm_c_remove "${@:2}"
}
# Helper function for mvm_remove invoked through mvm_call.
function mvm_c_remove() {
local key
key=${1}; shift
local storage_map_var_name
storage_map_var_name=${mvm['storage']}
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n storage_map_ref=${storage_map_var_name}
if [[ -z ${storage_map_ref["${key}"]:-} ]]; then
return 0
fi
local var_name=${storage_map_ref["${key}"]}
unset "storage_map_ref[${key}]"
local destructor
destructor=${mvm['destructor']}
"${destructor}" "${var_name}"
}
# Iterates over the key-mvc pairs and invokes a callback for each. The
# function also takes some extra parameters to forward to the
# callback. The callback will receive, in order, extra parameters, a
# key, an mvc name, and possibly some extra parameters from the
# iteration helper, if such exists for the mvm.
#
# Params:
#
# 1 - name of the mvm variable
# 2 - callback
# @ - extra parameters forwarded to the callback
function mvm_iterate() {
mvm_call "${1}" mvm_c_iterate "${@:2}"
}
# Helper function for mvm_iterate invoked through mvm_call.
function mvm_c_iterate() {
local callback
callback=${1}; shift
# rest are extra args passed to callback
local storage_map_var_name helper
storage_map_var_name=${mvm['storage']}
# shellcheck disable=SC2178 # shellcheck doesn't grok references to arrays
local -n storage_map_ref=${storage_map_var_name}
helper=${mvm['iteration_helper']}
local key value
if [[ -n "${helper}" ]]; then
for key in "${!storage_map_ref[@]}"; do
value=${storage_map_ref["${key}"]}
"${helper}" "${key}" "${value}" "${callback}" "${@}"
done
else
for key in "${!storage_map_ref[@]}"; do
value=${storage_map_ref["${key}"]}
"${callback}" "${@}" "${key}" "${value}"
done
fi
}
# debugging
declare -A MVM_DEBUG_NAMES=()
# Enables printing debugging info for a specified mvm.
#
# Params:
#
# 1 - name of the mvm variable
function mvm_debug_enable() {
local mvm_var_name=${1}; shift
MVM_DEBUG_NAMES["${mvm_var_name}"]=x
}
# Print debugging info about the mvm if debugging for it was enabled
# beforehand.
#
# Params:
#
# 1 - name of the mvm variable
# @ - strings to be printed
function mvm_debug() {
local name=${1}; shift
if [[ -n ${MVM_DEBUG_NAMES["${name}"]:-} ]]; then
info "MVM_DEBUG(${name}): ${*}"
fi
}
# Disables printing debugging info for a specified mvm.
#
# Params:
#
# 1 - name of the mvm variable
function mvm_debug_disable() {
local mvm_var_name=${1}; shift
unset "MVM_DEBUG_NAMES[${mvm_var_name}]"
}
fi

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,274 @@
#!/bin/bash
##
## Prints profile information in form of an inheritance tree and/or
## evaluation order.
##
## Parameters:
## -h: this help
## -ni: no inheritance tree
## -ne: no evaluation order
## -nh: no headers
##
## Environment variables:
## ROOT
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
: "${ROOT:=/}"
print_inheritance_tree=x
print_evaluation_order=x
print_headers=x
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h)
print_help
exit 0
;;
-ni)
print_inheritance_tree=
;;
-ne)
print_evaluation_order=
;;
-nh)
print_headers=
;;
*)
fail "unknown flag ${1}"
;;
esac
shift
done
all_repo_names=()
read -a all_repo_names -r < <(portageq get_repos "${ROOT}")
declare -A repo_data repo_data_r
# name to path
repo_data=()
# path to name
repo_data_r=()
for repo_name in "${all_repo_names[@]}"; do
repo_path=$(portageq get_repo_path "${ROOT}" "${repo_name}")
repo_path=$(realpath "${repo_path}")
repo_data["${repo_name}"]="${repo_path}"
repo_data_r["${repo_path}"]="${repo_name}"
done
unset all_repo_names
function get_repo_from_profile_path() {
local path
path=${1}; shift
local -n repo_dir_ref=${1}; shift
# shellcheck disable=SC2034 # it's a reference to external variable
repo_dir_ref="${path%/profiles/*}"
}
function repo_path_to_name() {
local path
path=${1}; shift
local -n name_ref=${1}; shift
# shellcheck disable=SC2034 # it's a reference to external variable
name_ref=${repo_data_r["${path}"]:-'<unknown>'}
}
function repeat_string() {
local str ntimes out_str_var_name
str=${1}; shift
ntimes=${1}; shift
out_str_var_name=${1}; shift
local -n out_str_ref="${out_str_var_name}"
if [[ ${ntimes} -eq 0 ]]; then
out_str_ref=""
return 0
elif [[ ${ntimes} -eq 1 ]]; then
out_str_ref="${str}"
return 0
fi
local add_one
add_one=$((ntimes % 2))
repeat_string "${str}${str}" $((ntimes / 2)) "${out_str_var_name}"
if [[ add_one -gt 0 ]]; then
out_str_ref+="${str}"
fi
}
function process_profile() {
local repo_name profile_path
repo_name=${1}; shift
profile_path=${1}; shift
local -n children_ref=${1}; shift
local parent_file line pp_new_repo_name new_profile_path pp_new_repo_path
local -a children
parent_file="${profile_path}/parent"
children=()
if [[ -e ${parent_file} ]]; then
while read -r line; do
if [[ ${line} = *:* ]]; then
pp_new_repo_name=${line%%:*}
if [[ -z ${pp_new_repo_name} ]]; then
pp_new_repo_name=${repo_name}
fi
pp_new_repo_path=${repo_data["${pp_new_repo_name}"]}
new_profile_path="${pp_new_repo_path}/profiles/${line#*:}"
children+=( "${pp_new_repo_name}" "${new_profile_path}" )
elif [[ ${line} = /* ]]; then
pp_new_repo_path=
get_repo_from_profile_path "${line}" pp_new_repo_path
pp_new_repo_name=
repo_path_to_name "${pp_new_repo_path}" pp_new_repo_name
children+=( "${pp_new_repo_name}" "${line}" )
else
pp_new_repo_path=$(realpath "${profile_path}/${line}")
children+=( "${repo_name}" "${pp_new_repo_path}" )
fi
done <"${parent_file}"
fi
# shellcheck disable=SC2034 # it's a reference to external variable
children_ref=( "${children[@]}" )
}
function get_profile_name() {
local repo_name profile_path
repo_name="${1}"; shift
profile_path="${1}"; shift
local -n profile_name_ref=${1}; shift
local repo_path profile_name
repo_path=${repo_data["${repo_name}"]}
profile_name=${profile_path#"${repo_path}/profiles/"}
# shellcheck disable=SC2034 # it's a reference to external variable
profile_name_ref="${profile_name}"
}
make_profile_path="${ROOT%/}/etc/portage/make.profile"
top_profile_dir_path=$(realpath "${make_profile_path}")
top_repo_path=
get_repo_from_profile_path "${top_profile_dir_path}" top_repo_path
top_repo_name=
repo_path_to_name "${top_repo_path}" top_repo_name
if [[ -n ${print_inheritance_tree} ]]; then
set -- '0' "${top_repo_name}" "${top_profile_dir_path}"
profile_tree=()
while [[ ${#} -gt 2 ]]; do
indent=${1}; shift
repo_name=${1}; shift
profile_path=${1}; shift
lines=
fork=
if [[ ${indent} -gt 0 ]]; then
if [[ ${indent} -gt 1 ]]; then
repeat_string '| ' $((indent - 1)) lines
fi
fork='+-'
fi
g_profile_name=
get_profile_name "${repo_name}" "${profile_path}" g_profile_name
profile_tree+=( "${lines}${fork}${repo_name}:${g_profile_name}" )
g_profile_children=()
process_profile "${repo_name}" "${profile_path}" g_profile_children
new_profiles=()
new_indent=$((indent + 1))
pc_idx=0
while [[ $((pc_idx + 1)) -lt "${#g_profile_children[@]}" ]]; do
new_repo_name=${g_profile_children["${pc_idx}"]}
new_profile_path=${g_profile_children[$((pc_idx + 1))]}
new_profiles+=( "${new_indent}" "${new_repo_name}" "${new_profile_path}" )
pc_idx=$((pc_idx + 2))
done
set -- "${new_profiles[@]}" "${@}"
done
if [[ -n ${print_headers} ]]; then
echo
echo 'profile inheritance tree:'
echo
fi
for line in "${profile_tree[@]}"; do
echo "${line}"
done
fi
if [[ -n ${print_evaluation_order} ]]; then
set -- "${top_repo_name}" "${top_profile_dir_path}" '0'
profile_eval=()
while [[ ${#} -gt 2 ]]; do
repo_name=${1}; shift
profile_path=${1}; shift
num_parents=${1}; shift
# each parent is a repo name and profile path, so two items for each parent
num_parent_items=$((num_parents * 2))
parents=( "${@:1:${num_parent_items}}" )
shift "${num_parent_items}"
g_profile_children=()
process_profile "${repo_name}" "${profile_path}" g_profile_children
new_args=()
if [[ ${#g_profile_children[@]} -eq 0 ]]; then
to_evaluate=( "${repo_name}" "${profile_path}" "${parents[@]}" )
te_idx=0
while [[ $((te_idx + 1)) -lt "${#to_evaluate[@]}" ]]; do
new_repo_name=${to_evaluate["${te_idx}"]}
new_profile_path=${to_evaluate[$((te_idx + 1))]}
g_new_profile_name=
get_profile_name "${new_repo_name}" "${new_profile_path}" g_new_profile_name
profile_eval+=( "${new_repo_name}:${g_new_profile_name}" )
te_idx=$((te_idx + 2))
done
else
last_idx=$(( ${#g_profile_children[@]} - 2 ))
pc_idx=0
while [[ $((pc_idx + 1)) -lt "${#g_profile_children[@]}" ]]; do
new_repo_name=${g_profile_children["${pc_idx}"]}
new_profile_path=${g_profile_children[$((pc_idx + 1))]}
new_args+=( "${new_repo_name}" "${new_profile_path}" )
if [[ pc_idx -eq last_idx ]]; then
new_args+=( $((num_parents + 1)) "${repo_name}" "${profile_path}" "${parents[@]}" )
else
new_args+=( 0 )
fi
pc_idx=$((pc_idx + 2))
done
fi
set -- "${new_args[@]}" "${@}"
done
if [[ -n ${print_headers} ]]; then
echo
echo 'profile evaluation order:'
echo
fi
for line in "${profile_eval[@]}"; do
echo "${line}"
done
fi

View File

@ -0,0 +1,206 @@
#!/usr/bin/python3
# The package list file is a document that consists of a header and an
# empty-line-separated list of package groups. Header is a list of all
# lines until the first package group. Package group is a list of
# packages of the same category and possibly some related
# comments. The comments are usually about packages that are
# temporarily excluded from the list. So the comments usually have two
# parts - the free form part and a commented-out package list part;
# the example would be:
#
# # Temporarily excluded from automated updates, because reasons.
# #
# # sys-devel/whatever
#
# The script tries to preserve the comments and its ordering, so it
# associates the free form part to the package name.
#
# The script also deduplicates the packages while sorting. An edge
# case is when a package appears multiple times and is not
# commented-out at least once - all commented out entries are dropped.
#
# Implementation-wise, the document has a list of lines being a
# header, a list of free form comments and a map of category name to a
# group. A group is a list of packages, where each package has a name,
# information if it's commented out and may have a free form comment
# associated with it.
import re
import sys
class FreeForm:
def __init__(self, lines):
self.lines = lines
class Pkg:
def __init__(self, idx, name, out):
self.free_form_idx = idx
self.name = name
self.commented_out = out
class Group:
def __init__(self):
self.pkgs = []
self.pkg_names_set = set()
class Document:
def __init__(self):
self.header = []
self.free_forms = []
self.groups = {}
class Reader:
category_or_pkg_pattern = re.compile("^[a-z0-9-]+(?:/[A-Za-z0-9-_+]+)?$")
parsing_header = 1
parsing_group = 2
parsing_comment = 3
def __init__(self, doc):
self.doc = doc
self.parsing_stage = Reader.parsing_header
self.current_comments = []
self.free_form_idx_for_next_pkg = None
def get_group(self, category):
if category not in self.doc.groups:
new_group = Group()
self.doc.groups[category] = new_group
return new_group
return self.doc.groups[category]
def add_pkg_impl(self, idx, name, out):
category = name.split('/', 1)[0]
group = self.get_group(category)
if name in group.pkg_names_set:
if not out:
for pkg in group.pkgs:
if pkg.name == name:
pkg.commented_out = False
break
else:
group.pkg_names_set.add(name)
group.pkgs += [Pkg(idx, name, out)]
return True
return False
def add_pkg(self, name):
if self.add_pkg_impl(self.free_form_idx_for_next_pkg, name, False):
self.free_form_idx_for_next_pkg = None
class CommentBatch:
def __init__(self, ff_lines, p_lines):
self.free_form_lines = ff_lines
self.pkg_lines = p_lines
def get_batches(self):
batches = []
free_form_lines = []
pkg_lines = []
for line in self.current_comments:
line = line.lstrip('#').strip()
if not line:
if not pkg_lines:
free_form_lines += [line]
elif Reader.category_or_pkg_pattern.match(line):
pkg_lines += [line]
else:
if pkg_lines:
while not free_form_lines[-1]:
free_form_lines = free_form_lines[:-1]
batches += [Reader.CommentBatch(free_form_lines, pkg_lines)]
free_form_lines = []
pkg_lines = []
free_form_lines += [line]
self.current_comments = []
if free_form_lines or pkg_lines:
batches += [Reader.CommentBatch(free_form_lines, pkg_lines)]
return batches
def process_current_comments(self):
for batch in self.get_batches():
free_form_idx = None
if batch.free_form_lines:
free_form_idx = len(self.doc.free_forms)
self.doc.free_forms += [FreeForm(batch.free_form_lines)]
if batch.pkg_lines:
for line in batch.pkg_lines:
self.add_pkg_impl(free_form_idx, line, True)
else:
self.free_form_idx_for_next_pkg = free_form_idx
def read(self, input):
while line := input.readline():
line = line.strip()
if self.parsing_stage == Reader.parsing_header:
if not line:
self.parsing_stage = Reader.parsing_group
elif line.startswith('#'):
self.doc.header += [line]
else:
self.parsing_stage = Reader.parsing_group
self.add_pkg(line)
elif self.parsing_stage == Reader.parsing_group:
if not line:
pass
elif line.startswith('#'):
self.current_comments += [line]
self.parsing_stage = Reader.parsing_comment
else:
self.add_pkg(line)
elif self.parsing_stage == Reader.parsing_comment:
if not line:
self.parsing_stage = Reader.parsing_group
self.process_current_comments()
elif line.startswith('#'):
self.current_comments += [line]
else:
self.parsing_stage = Reader.parsing_group
self.process_current_comments()
self.add_pkg(line)
if self.current_comments:
self.process_current_comments()
class Writer:
def __init__(self, doc):
self.doc = doc
def write(self, output):
output_lines = []
if self.doc.header:
output_lines += self.doc.header
output_lines += ['']
for category in sorted(self.doc.groups):
last_free_form_idx = None
for pkg in sorted(self.doc.groups[category].pkgs, key=lambda pkg: pkg.name):
if pkg.free_form_idx != last_free_form_idx:
last_free_form_idx = pkg.free_form_idx
if pkg.free_form_idx is not None:
for line in self.doc.free_forms[pkg.free_form_idx].lines:
if line:
output_lines += [f"# {line}"]
else:
output_lines += ['#']
if pkg.commented_out:
output_lines += [f"# {pkg.name}"]
else:
output_lines += [f"{pkg.name}"]
output_lines += ['']
while not output_lines[0]:
output_lines = output_lines[1:]
while not output_lines[-1]:
output_lines = output_lines[:-1]
for line in output_lines:
print(line, file=output)
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"1 argument expected, got {len(sys.argv) - 1}", file=sys.stderr)
sys.exit(1)
filename = sys.argv[1]
doc = Document()
with open(filename, 'r', encoding='UTF-8') as file:
reader = Reader(doc)
reader.read(file)
writer = Writer(doc)
writer.write(sys.stdout)

316
pkg_auto/impl/sync_with_gentoo.sh Executable file
View File

@ -0,0 +1,316 @@
#!/bin/bash
##
## Used for syncing with gentoo. Needs to be called from the
## toplevel-directory of portage-stable. If syncing everything or
## syncing metadata/glsa specifically, it is expected that the Gentoo
## repo will have the GLSA files stored in metadata/glsa too.
##
## Parameters:
## -h: this help
## -b: be brief, print only names of changed entries and errors
## -s: skip adding source git commit hash information to commits
##
## Positional:
## 0: Gentoo repository
## #: Entries to update (can be a package name, eclass, category, some special
## directories like profiles or . for everything)
##
## Example invocations:
##
## sync_with_gentoo -h
##
## Print a help message.
##
## sync_with_gentoo dev-libs/nettle app-crypt/argon2
##
## This will update the packages, each in a separate commit. The
## commit message will contain the commit hash from gentoo repo.
##
## sync_with_gentoo dev-libs
##
## This will update all the packages in dev-libs category. The
## commit message will contain the commit hash from gentoo repo.
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
BRIEF=
SKIP_GIT_INFO=
while true; do
case ${1-} in
-h)
print_help
exit 0
;;
-b)
BRIEF=x
shift
;;
-s)
SKIP_GIT_INFO=x
shift
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ $# -lt 2 ]]; then
fail 'expected at least two positional parameters: a Gentoo repository and at least one package, use -h to print help'
fi
if [[ ! -e 'profiles/repo_name' ]]; then
fail 'sync is only possible from ebuild packages top-level directory (a directory from which "./profiles/repo_name" is accessible)'
fi
function vcall() {
if [[ -z ${BRIEF} ]]; then
"${@}"
fi
}
function bcall() {
if [[ -n ${BRIEF} ]]; then
"${@}"
fi
}
GENTOO=$(realpath "${1}"); shift
# rest are package names
if [[ $(realpath '.') = "${GENTOO}" ]]; then
fail 'trying to sync within a Gentoo repo?'
fi
if [[ -z ${SKIP_GIT_INFO} ]] && [[ ! -e ${GENTOO}/.git ]]; then
info "Skipping adding source git commit hash information to commits, ${GENTOO@Q} is not a git repository"
SKIP_GIT_INFO=x
fi
glsa_repo=${GENTOO}/metadata/glsa
if [[ -z ${SKIP_GIT_INFO} ]] && [[ -e ${glsa_repo} ]] && [[ ! -e ${glsa_repo}/.git ]] && [[ $(git -C "${GENTOO}" status --porcelain -- metadata/glsa) = '?? metadata/glsa' ]]; then
info "Skipping adding source git commit hash information to commits, ${glsa_repo@Q} exists, but it is not a git repository and is not a part of Gentoo git repository"
SKIP_GIT_INFO=x
fi
unset glsa_repo
# Synchronizes given path with its Gentoo counterpart. Returns true if
# there were changes.
#
# Params:
#
# 1 - path within ebuild repo
function sync_git_prepare() {
local path
path=${1}; shift
local gentoo_path
gentoo_path="${GENTOO}/${path}"
if [[ ! -e "${gentoo_path}" ]]; then
info "no ${path@Q} in Gentoo repository"
if [[ ${path} = 'metadata/glsa' ]]; then
info "did you forget to clone https://gitweb.gentoo.org/data/glsa.git/ into ${gentoo_path@Q}?"
fi
return 1
fi
local -a rsync_opts=( --archive --delete-before )
case ${path} in
profiles)
rsync_opts+=( --exclude /profiles/repo_name )
;;
esac
local parent
dirname_out "${path}" parent
mkdir --parents "${parent}"
rsync "${rsync_opts[@]}" "${gentoo_path}" "${parent}"
if [[ -n $(git status --porcelain -- "${path}") ]]; then
bcall info "updated ${path}"
git add "${path}"
return 0
fi
return 1
}
# Creates a git commit. If checking Gentoo commit ID is enabled the
# given path is used to get the ID of the commit with the last change
# in the path. Name parameter is used for denoting which part has
# changed, and sync parameter to denote if the commit is about adding
# new package or updating an existing one.
#
# Params:
#
# 1 - path
# 2 - name
# 3 - not empty if existing package was updated, or an empty string if
# the package is new
function commit_with_gentoo_sha() {
local path name sync
path=${1}; shift
name=${1}; shift
sync=${1:-}; shift
local -a commit_extra=()
if [[ -z ${SKIP_GIT_INFO} ]]; then
local commit
commit=$(git -C "${GENTOO}/${path}" log --pretty=oneline -1 -- . | cut -f1 -d' ')
commit_extra+=( --message "It's from Gentoo commit ${commit}." )
unset commit
fi
commit_msg="${name}: Add from Gentoo"
if [[ -n "${sync}" ]]; then
commit_msg="${name}: Sync with Gentoo"
fi
git commit --quiet --message "${commit_msg}" "${commit_extra[@]}"
GIT_PAGER='cat' vcall git show --stat
}
# Simple path sync and commit; takes the contents from Gentoo at the
# given path and puts it in the repo.
#
# 1 - path to sync
# 2 - name for commit message
function path_sync() {
local path name
path=${1}; shift
name=${1}; shift
local sync
sync=''
if [[ -e "${path}" ]]; then
sync='x'
fi
if sync_git_prepare "${path}"; then
commit_with_gentoo_sha "${path}" "${name}" "${sync}"
else
vcall info "no changes in ${path}"
fi
}
# Goes over the given directory and syncs its subdirectories or
# files. No commit is created.
function prepare_dir() {
local dir
dir=${1}; shift
local pkg mod=''
for pkg in "${dir}/"*; do
if sync_git_prepare "${pkg}"; then
mod=x
fi
done
if [[ -n ${mod} ]]; then
return 0
fi
return 1
}
# Synces entire category of packages and creates a commit. Note that
# if the category already exists, no new packages will be added.
#
# Params:
#
# 1 - path to the category directory
function category_sync() {
local path
path=${1}; shift
if [[ ! -e "${path}" ]]; then
if sync_git_prepare "${path}"; then
commit_with_gentoo_sha "${path}" "${path}"
fi
else
if prepare_dir "${path}"; then
commit_with_gentoo_sha "${path}" "${path}" 'x'
fi
fi
}
# Synces entire repo. No new packages will be added.
function everything_sync() {
local path mod
for path in *; do
case ${path} in
licenses|profiles|scripts)
if sync_git_prepare "${path}"; then
mod=x
fi
;;
metadata)
# do only metadata updates
if sync_git_prepare metadata/glsa; then
mod=x
fi
;;
eclass|virtual|*-*)
if prepare_dir "${path}"; then
mod=x
fi
;;
changelog|*.md)
# ignore those
:
;;
*)
info "Unknown entry ${path@Q}, ignoring"
;;
esac
done
if [[ -n ${mod} ]]; then
commit_with_gentoo_sha '.' '*' 'x'
fi
}
shopt -s extglob
for cpn; do
cpn=${cpn%%*(/)}
case ${cpn} in
.)
everything_sync
;;
licenses|profiles|scripts)
path_sync "${cpn}" "${cpn}"
;;
eclass/*.eclass)
path_sync "${cpn}" "${cpn%.eclass}"
;;
metadata/glsa)
path_sync "${cpn}" "${cpn}"
;;
metadata)
fail "metadata directory can't be synced, did you mean metadata/glsa?"
;;
virtual/*/*|*-*/*/*)
fail "invalid thing to sync: ${cpn}"
;;
virtual/*|*-*/*)
path_sync "${cpn}" "${cpn}"
;;
eclass|virtual|*-*)
category_sync "${cpn}"
;;
*)
fail "invalid thing to sync: ${cpn}"
;;
esac
done

211
pkg_auto/impl/util.sh Normal file
View File

@ -0,0 +1,211 @@
#!/bin/bash
if [[ -z ${__UTIL_SH_INCLUDED__:-} ]]; then
__UTIL_SH_INCLUDED__=x
# Works like dirname, but without spawning new processes.
#
# Params:
#
# 1 - path to operate on
# 2 - name of a variable which will contain a dirname of the path
function dirname_out() {
local path dir_var_name
path=${1}; shift
dir_var_name=${1}; shift
local -n dir_ref=${dir_var_name}
if [[ -z ${path} ]]; then
dir_ref='.'
return 0
fi
local cleaned_up dn
# strip trailing slashes
cleaned_up=${path%%*(/)}
# strip duplicated slashes
cleaned_up=${cleaned_up//+(\/)/\/}
# strip last component
dn=${cleaned_up%/*}
if [[ -z ${dn} ]]; then
dir_ref='/'
return 0
fi
if [[ ${cleaned_up} = "${dn}" ]]; then
dir_ref='.'
return 0
fi
# shellcheck disable=SC2034 # it's a reference to external variable
dir_ref=${dn}
}
# Works like basename, but without spawning new processes.
#
# Params:
#
# 1 - path to operate on
# 2 - name of a variable which will contain a basename of the path
function basename_out() {
local path base_var_name
path=${1}; shift
base_var_name=${1}; shift
local -n base_ref=${base_var_name}
if [[ -z ${path} ]]; then
base_ref=''
return 0
fi
local cleaned_up dn
# strip trailing slashes
cleaned_up=${path%%*(/)}
if [[ -z ${cleaned_up} ]]; then
base_ref='/'
return 0
fi
# strip duplicated slashes
cleaned_up=${cleaned_up//+(\/)/\/}
# keep last component
dn=${cleaned_up##*/}
# shellcheck disable=SC2034 # it's a reference to external variable
base_ref=${dn}
}
if [[ ${BASH_SOURCE[-1]##*/} = 'util.sh' ]]; then
THIS=${BASH}
basename_out "${THIS}" THIS_NAME
THIS_DIR=.
else
THIS=${BASH_SOURCE[-1]}
basename_out "${THIS}" THIS_NAME
dirname_out "${THIS}" THIS_DIR
fi
THIS=$(realpath "${THIS}")
THIS_DIR=$(realpath "${THIS_DIR}")
dirname_out "${BASH_SOURCE[0]}" PKG_AUTO_IMPL_DIR
PKG_AUTO_IMPL_DIR=$(realpath "${PKG_AUTO_IMPL_DIR}")
# shellcheck disable=SC2034 # may be used by scripts sourcing this file
PKG_AUTO_DIR=$(realpath "${PKG_AUTO_IMPL_DIR}/..")
# Prints an info line.
#
# Params:
#
# @ - strings to print
function info() {
printf '%s: %s\n' "${THIS_NAME}" "${*}"
}
# Prints info lines.
#
# Params:
#
# @ - lines to print
function info_lines() {
printf '%s\n' "${@/#/"${THIS_NAME}: "}"
}
# Prints an info to stderr and fails the execution.
#
# Params:
#
# @ - strings to print
function fail() {
info "${@}" >&2
exit 1
}
# Prints infos to stderr and fails the execution.
#
# Params:
#
# @ - lines to print
function fail_lines() {
info_lines "${@}" >&2
exit 1
}
# Yells a message.
#
# Params:
#
# @ - strings to yell
function yell() {
echo
echo '!!!!!!!!!!!!!!!!!!'
echo " ${*}"
echo '!!!!!!!!!!!!!!!!!!'
echo
}
# Prints help. Help is taken from the lines prefixed with double
# hashes in the top sourcer of this file.
function print_help() {
if [[ ${THIS} != "${BASH}" ]]; then
grep '^##' "${THIS}" | sed -e 's/##[[:space:]]*//'
fi
}
# Joins passed strings with a given delimiter.
#
# Params:
#
# 1 - name of a variable that will contain the joined result
# 2 - delimiter
# @ - strings to join
function join_by() {
local output_var_name delimiter first
output_var_name=${1}; shift
delimiter=${1}; shift
first=${1-}
if shift; then
printf -v "${output_var_name}" '%s' "${first}" "${@/#/${delimiter}}";
else
local -n output_ref=${output_var_name}
# shellcheck disable=SC2034 # it's a reference to external variable
output_ref=''
fi
}
# Checks if directory is empty, returns true if so, otherwise false.
#
# Params:
#
# 1 - path to a directory
function dir_is_empty() {
local dir
dir=${1}; shift
[[ -z $(echo "${dir}"/*) ]]
}
# Just like diff, but ignores the return value.
function xdiff() {
diff "${@}" || :
}
# Just like grep, but ignores the return value.
function xgrep() {
grep "${@}" || :
}
# Strips leading and trailing whitespace from the passed parameter.
#
# Params:
#
# 1 - string to strip
# 2 - name of a variable where the result of stripping will be stored
function strip_out() {
local l
l=${1}; shift
local -n out_ref=${1}; shift
local t
t=${l}
t=${t/#+([[:space:]])}
t=${t/%+([[:space:]])}
# shellcheck disable=SC2034 # it's a reference to external variable
out_ref=${t}
}
fi

102
pkg_auto/inside_sdk_container.sh Executable file
View File

@ -0,0 +1,102 @@
#!/bin/bash
##
## Gathers information about SDK and board packages. Also collects
## info about actual build deps of board packages, which may be useful
## for verifying if SDK provides those.
##
## Reports generated:
## sdk-pkgs - contains package information for SDK
## sdk-pkgs-kv - contains package information with key values (USE, PYTHON_TARGETS, CPU_FLAGS_X86) for SDK
## board-pkgs - contains package information for board for chosen architecture
## board-bdeps - contains package information with key values (USE, PYTHON_TARGETS, CPU_FLAGS_X86) of board build dependencies
## sdk-profiles - contains a list of profiles used by the SDK, in evaluation order
## board-profiles - contains a list of profiles used by the board for the chosen architecture, in evaluation order
## sdk-package-repos - contains package information with their repos for SDK
## board-package-repos - contains package information with their repos for board
## sdk-emerge-output - contains raw emerge output for SDK being a base for other reports
## board-emerge-output - contains raw emerge output for board being a base for other reports
## sdk-emerge-output-filtered - contains only lines with package information for SDK
## board-emerge-output-filtered - contains only lines with package information for board
## sdk-emerge-output-junk - contains only junk lines for SDK
## board-emerge-output-junk - contains only junk lines for board
## *-warnings - warnings printed by emerge or other tools
##
## Parameters:
## -h: this help
##
## Positional:
## 1 - architecture (amd64 or arm64)
## 2 - reports directory
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/inside_sdk_container_lib.sh"
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h)
print_help
exit 0
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ ${#} -ne 2 ]]; then
fail 'Expected two parameters: board architecture and reports directory'
fi
arch=${1}; shift
reports_dir=${1}; shift
mkdir -p "${reports_dir}"
set_eo "${reports_dir}"
echo 'Running pretend-emerge to get complete report for SDK'
package_info_for_sdk >"${SDK_EO}" 2>"${SDK_EO_W}"
echo 'Running pretend-emerge to get complete report for board'
package_info_for_board "${arch}" >"${BOARD_EO}" 2>"${BOARD_EO_W}"
ensure_no_errors
echo 'Separating emerge info from junk in SDK emerge output'
filter_sdk_eo >"${SDK_EO_F}" 2>>"${SDK_EO_W}"
junk_sdk_eo >"${SDK_EO}-junk" 2>>"${SDK_EO_W}"
echo 'Separating emerge info from junk in board emerge output'
filter_board_eo "${arch}" >"${BOARD_EO_F}" 2>>"${BOARD_EO_W}"
junk_board_eo >"${BOARD_EO}-junk" 2>>"${BOARD_EO_W}"
ensure_valid_reports
echo 'Generating SDK packages listing'
versions_sdk >"${reports_dir}/sdk-pkgs" 2>"${reports_dir}/sdk-pkgs-warnings"
echo 'Generating SDK packages listing with key-values (USE, PYTHON_TARGETS CPU_FLAGS_X86, etc)'
versions_sdk_with_key_values >"${reports_dir}/sdk-pkgs-kv" 2>"${reports_dir}/sdk-pkgs-kv-warnings"
echo 'Generating board packages listing'
versions_board >"${reports_dir}/board-pkgs" 2>"${reports_dir}/board-pkgs-warnings"
echo 'Generating board packages bdeps listing'
board_bdeps >"${reports_dir}/board-bdeps" 2>"${reports_dir}/board-bdeps-warnings"
echo 'Generating SDK profiles evaluation list'
ROOT=/ "${PKG_AUTO_IMPL_DIR}/print_profile_tree.sh" -ni -nh >"${reports_dir}/sdk-profiles" 2>"${reports_dir}/sdk-profiles-warnings"
echo 'Generating board profiles evaluation list'
ROOT="/build/${arch}-usr" "${PKG_AUTO_IMPL_DIR}/print_profile_tree.sh" -ni -nh >"${reports_dir}/board-profiles" 2>"${reports_dir}/board-profiles-warnings"
echo 'Generating SDK package source information'
package_sources_sdk >"${reports_dir}/sdk-package-repos" 2>"${reports_dir}/sdk-package-repos-warnings"
echo 'Generating board package source information'
package_sources_board >"${reports_dir}/board-package-repos" 2>"${reports_dir}/board-package-repos-warnings"
echo "Cleaning empty warning files"
clean_empty_warning_files "${reports_dir}"

60
pkg_auto/sync_packages.sh Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
##
## Synces the packages with Gentoo.
##
## Parameters:
## -f: remove reports directory if it exists at startup
## -w: path to use for workdir
## -h: this help
##
## Positional:
## 1: config file
## 2: new branch name with updates
## 3: gentoo repo
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/pkg_auto_lib.sh"
workdir=''
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h)
print_help
exit 0
;;
-w)
if [[ -z ${2:-} ]]; then
fail 'missing value for -w'
fi
workdir=${2}
shift 2
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ ${#} -ne 3 ]]; then
fail 'expected three positional parameters: a config file, a final branch name and a path to Gentoo repo'
fi
config_file=${1}; shift
saved_branch_name=${1}; shift
gentoo=${1}; shift
setup_workdir_with_config "${workdir}" "${config_file}"
perform_sync_with_gentoo "${gentoo}"
save_new_state "${saved_branch_name}"

60
pkg_auto/update_packages.sh Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
##
## Updates the packages
##
## Parameters:
## -w: path to use for workdir
## -h: this help
##
## Positional:
## 1: config file
## 2: new branch name with updates
## 3: gentoo repo
##
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/impl/util.sh"
source "${PKG_AUTO_IMPL_DIR}/pkg_auto_lib.sh"
workdir=''
while [[ ${#} -gt 0 ]]; do
case ${1} in
-h)
print_help
exit 0
;;
-w)
if [[ -z ${2:-} ]]; then
fail 'missing value for -w'
fi
workdir=${2}
shift 2
;;
--)
shift
break
;;
-*)
fail "unknown flag '${1}'"
;;
*)
break
;;
esac
done
if [[ ${#} -ne 3 ]]; then
fail 'expected three positional parameters: a config file, a final branch name and a path to Gentoo repo'
fi
config_file=${1}; shift
saved_branch_name=${1}; shift
gentoo=${1}; shift
setup_workdir_with_config "${workdir}" "${config_file}"
perform_sync_with_gentoo "${gentoo}"
save_new_state "${saved_branch_name}"
generate_package_update_reports