flatcar-scripts/pkg_auto/impl/pkg_auto_lib.sh
Krzesimir Nowak eb8684e88b pkg-auto: Run emerge report generation in jobs
We can run report generation for old and new in parallel in two
separate processes. Ought to be a bit less of wait.

This is more or less straightforward parallelization, since there are
only two jobs running. The only thing that needs taking care of is
forwarding job's output to the terminal and handling job failures.
2025-08-21 14:52:16 +02:00

3302 lines
112 KiB
Bash

#!/bin/bash
#
# TODO:
#
# - Generate a report about missing build deps of board packages in
# sdk. These reports can be generated by processing sdk-pkgs-kv and
# board-bdeps reports, I think.
#
# - Mount Gentoo repo into the SDK container and set up emerge to use
# Gentoo as a primary repo, and portage-stable and coreos-overlay as
# overlays. That way if an updated package pulls in a new package we
# can notice it when it comes from Gentoo (emerge reports also
# source repo like sys-libs/glibc-2.35-r5::gentoo or something like
# this). This would make this script more robust.
#
# - Instead of having a list of packages to update, rather update them
# all in a single commit and have a list of exclusions. The reason
# is that, at this point, almost all of the packages in
# portage-stable are automatically updated, exclusions being usually
# temporary, so it would be better to have a short file with
# temporary exclusions rather than a long file with some commented
# out entries. This probably would render the sort_packages_list.py
# script unnecessary. On the other hand, the current mode of
# operation may be still useful for the coreos-overlay packages,
# because none of them are under automation (some are, though, under
# an ad-hoc automation via github actions).
#
# - Handle package appearance or disappearance. Currently, when a
# package ends up being unused (so it exists, but is not picked up,
# because some other package stopped depending on it) or removed,
# the package ends up in the manual-work-needed file. This probably
# could be handled as an entry in the summary stubs about being
# dropped.
#
# - Find unused packages and eclasses.
#
# - The rename handling should probably also change all instances of
# the old name everywhere outside portage-stable, otherwise emerge
# may fail when some our ebuild still uses the old name, maybe.
#
# Needed to be enabled here to parse some globs inside the functions.
shopt -s extglob
# Saner defaults.
shopt -s nullglob
shopt -s dotglob
if [[ -z ${__PKG_AUTO_LIB_SH_INCLUDED__:-} ]]; then
__PKG_AUTO_LIB_SH_INCLUDED__=x
source "$(dirname "${BASH_SOURCE[0]}")/util.sh"
source "${PKG_AUTO_IMPL_DIR}/cleanups.sh"
source "${PKG_AUTO_IMPL_DIR}/debug.sh"
source "${PKG_AUTO_IMPL_DIR}/gentoo_ver.sh"
source "${PKG_AUTO_IMPL_DIR}/jobs_lib.sh"
source "${PKG_AUTO_IMPL_DIR}/md5_cache_diff_lib.sh"
# Sets up the workdir using the passed config. The config can be
# created basing on the config_template file or using the
# generate_config script.
#
# The path to the workdir can be empty - the function will then create
# a temporary directory.
#
# This also sets the WORKDIR global variable, allowing other function
# to be invoked.
#
# Params:
#
# 1 - path to the workdir
# 2 - path to the config file
function setup_workdir_with_config() {
local workdir file
workdir=${1}; shift
config_file=${1}; shift
local cfg_scripts cfg_aux cfg_reports cfg_old_base cfg_new_base cfg_sdk_image_override
local -a cfg_cleanups cfg_debug_packages
# some defaults
cfg_old_base='origin/main'
cfg_new_base=''
cfg_cleanups=('ignore')
cfg_sdk_image_override=''
cfg_debug_packages=()
local line key value swwc_stripped var_name arch
while read -r line; do
strip_out "${line%%:*}" swwc_stripped
key=${swwc_stripped}
strip_out "${line#*:}" swwc_stripped
value=${swwc_stripped}
if [[ -z ${value} ]]; then
fail "empty value for ${key} in config"
fi
case ${key} in
scripts|aux|reports)
var_name="cfg_${key//-/_}"
local -n var_ref=${var_name}
var_ref=$(realpath "${value}")
unset -n var_ref
;;
old-base|new-base|sdk-image-override)
var_name="cfg_${key//-/_}"
local -n var_ref=${var_name}
var_ref=${value}
unset -n var_ref
;;
cleanups|debug-packages)
var_name="cfg_${key//-/_}"
mapfile -t "${var_name}" <<<"${value//,/$'\n'}"
;;
esac
done < <(cat_meaningful "${config_file}")
if [[ -z "${cfg_new_base}" ]]; then
cfg_new_base=${cfg_old_base}
fi
for key in scripts aux reports; do
var_name="cfg_${key//-/_}"
if [[ -z "${!var_name}" ]]; then
fail "${key} was not specified in config"
fi
done
setup_cleanups "${cfg_cleanups[@]}"
setup_workdir "${workdir}"
add_cleanup "rm -f ${WORKDIR@Q}/config"
cp -a "${config_file}" "${WORKDIR}/config"
setup_worktrees_in_workdir "${cfg_scripts}" "${cfg_old_base}" "${cfg_new_base}" "${cfg_reports}" "${cfg_aux}"
if [[ -n ${cfg_sdk_image_override} ]]; then
override_sdk_image_name "${cfg_sdk_image_override}"
fi
pkg_debug_add "${cfg_debug_packages[@]}"
}
# Goes over the list of automatically updated packages and synces them
# with packages from Gentoo repo. Cleans up missing packages.
#
# The function can only be called after the workdir has been set up
# with setup_workdir_with_config.
#
# Params:
#
# 1 - a path to the Gentoo repo
function perform_sync_with_gentoo() {
local gentoo
gentoo=$(realpath "${1}"); shift
run_sync "${gentoo}"
handle_missing_in_scripts
handle_missing_in_gentoo "${gentoo}"
}
# Generates package update reports. Duh.
#
# The function can only be called after the workdir has been set up
# with setup_workdir_with_config.
#
# The location of the reports is specified in the config that was
# passed to setup_workdir_with_config.
function generate_package_update_reports() {
generate_sdk_reports
handle_gentoo_sync
}
# Saves the new state to a git branch in scripts.
#
# The function can only be called after the workdir has been set up
# with setup_workdir_with_config.
#
# Params:
#
# 1 - name of the new branch
function save_new_state() {
local branch_name
branch_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
info "saving new state to branch ${branch_name}"
git -C "${SCRIPTS}" branch --force "${branch_name}" "${NEW_STATE_BRANCH}"
}
#
#
# Implementation details, do not use directly in scripts sourcing this file.
#
#
# Creates a workdir, the path to which is stored in WORKDIR global
# variable.
#
# 1 - predefined work directory path (optional)
function setup_workdir() {
local workdir
workdir=${1:-}
if [[ -z "${workdir}" ]]; then
workdir=$(mktemp --tmpdir --directory 'pkg-auto-workdir.XXXXXXXX')
else
if [[ -e ${workdir} ]] && [[ ! -d ${workdir} ]]; then
fail "Expected ${workdir@Q} to be a directory"
fi
if [[ -d ${workdir} ]] && ! dir_is_empty "${workdir}"; then
fail "Expected ${workdir@Q} to be an empty directory"
fi
fi
declare -g WORKDIR
WORKDIR=$(realpath "${workdir}")
add_cleanup "rmdir ${WORKDIR@Q}"
mkdir -p "${WORKDIR}"
setup_initial_globals_file
}
# Sets up worktrees for the old and new state inside WORKDIR. Creates
# the globals file inside WORKDIR.
#
# 1 - path to scripts repo
# 2 - base for the old state worktree (e.g. origin/main)
# 3 - base for the new state worktree (e.g. origin/main)
# 4 - path to reports directory
function setup_worktrees_in_workdir() {
local scripts old_base new_base reports_dir aux_dir
scripts=${1}; shift
old_base=${1}; shift
new_base=${1}; shift
reports_dir=${1}; shift
aux_dir=${1}; shift
local old_state new_state
old_state="${WORKDIR}/old_state"
new_state="${WORKDIR}/new_state"
# create reports directory now - there may be some developer
# warnings afoot
mkdir -p "${reports_dir}"
setup_worktree "${scripts}" "${old_base}" "old-state-${RANDOM}" "${old_state}"
setup_worktree "${scripts}" "${new_base}" "new-state-${RANDOM}" "${new_state}"
extend_globals_file "${scripts}" "${old_state}" "${new_state}" "${reports_dir}" "${aux_dir}"
}
# Adds an overridden SDK image name to the globals file.
#
# Params:
#
# 1 - image name
function override_sdk_image_name() {
local image_name=${1}; shift
append_to_globals "SDK_IMAGE=${image_name@Q}"
}
# Appends passed lines to the globals file.
#
# Params:
#
# @ - lines to append
function append_to_globals() {
local globals_file
globals_file="${WORKDIR}/globals"
if [[ ! -e "${globals_file}" ]]; then
fail "globals not set yet in workdir"
fi
lines_to_file "${globals_file}" "${@}"
}
# Processes the update files in portage-stable and coreos-overlay to
# figure out potential package renames. The results are stored in the
# passed map.
#
# Params:
#
# 1 - name of a map variable; will be a mapping of old package name to
# new package name
function process_profile_updates_directory() {
local from_to_map_var_name=${1}; shift
local -a ppud_ordered_names
get_ordered_update_filenames ppud_ordered_names
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local bf ps_f co_f pkg f line old new
local -a fields
local -A from_to_f=()
mvm_declare ppud_to_from_set_mvm mvm_mvc_set
for bf in "${ppud_ordered_names[@]}"; do
# coreos-overlay updates may overwrite updates from
# portage-stable, but only from the file of the same name
ps_f=${NEW_PORTAGE_STABLE}/profiles/updates/${bf}
co_f=${NEW_COREOS_OVERLAY}/profiles/updates/${bf}
for f in "${ps_f}" "${co_f}"; do
if [[ ! -f ${f} ]]; then
continue
fi
while read -r line; do
if [[ ${line} != 'move '* ]]; then
# other possibility is "slotmove" - we don't care
# about those.
continue
fi
mapfile -t fields <<<"${line// /$'\n'}"
if [[ ${#fields[@]} -ne 3 ]]; then
fail_lines \
"Malformed line ${line@Q} in updates file ${f@Q}." \
"The line should have 3 fields, has ${#fields[*]}."
fi
from_to_f["${fields[1]}"]=${fields[2]}
done <"${f}"
done
for old in "${!from_to_f[@]}"; do
new=${from_to_f["${old}"]}
update_rename_maps "${from_to_map_var_name}" ppud_to_from_set_mvm "${old}" "${new}"
done
done
mvm_unset ppud_to_from_set_mvm
}
# Gets a merged and ordered list of update files from portage-stable
# and coreos-overlay and stores the in the passed array. The files
# have names like Q1-2018, Q1-2023, Q2-2019 and so on. We need to sort
# them by year, then by quarter.
#
# Params:
#
# 1 - name of a array variable where the ordered names will be stored
function get_ordered_update_filenames() {
local ordered_names_var_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -A names_set=()
local f
for f in "${NEW_PORTAGE_STABLE}/profiles/updates/"* "${NEW_COREOS_OVERLAY}/profiles/updates/"*; do
names_set["${f##*/}"]=x
done
mapfile -t "${ordered_names_var_name}" < <(printf '%s\n' "${!names_set[@]}" | sort --field-separator=- --key=2n --key=1n)
}
# Updates the rename map with the new "old to new package rename". It
# tries to be smart about the rename sequences (though not sure if
# this is necessary, really). If in older update file package foo was
# renamed to bar and in current update file the bar package is renamed
# to quux, then this function adds an entry about the "bar to quux"
# rename, but also updates the older entry about "foo to bar" rename
# to "foo to quux" rename.
#
# Params:
#
# 1 - name of the renames map variable; should be a mapping of old to
# new names
# 2 - name of the set mvm variable; should be a mapping of new name to
# a set of old names (a reverse mapping to the renames map)
# 3 - old name
# 4 - new name
function update_rename_maps() {
local -n ft_map_ref=${1}; shift
local tf_set_mvm_var_name=${1}; shift
local old_name=${1}; shift
local new_name=${1}; shift
local prev_new_name=${ft_map_ref["${old_name}"]:-}
if [[ -n ${prev_new_name} ]] && [[ ${prev_new_name} != "${new_name}" ]]; then
fail_lines \
"Invalid package rename from ${old_name@Q} to ${new_name@Q}." \
"There was already a rename from ${old_name@Q} to ${prev_new_name@Q}."
fi
local -a new_set=()
local urm_set_var_name
mvm_get "${tf_set_mvm_var_name}" "${old_name}" urm_set_var_name
if [[ -n ${urm_set_var_name} ]]; then
local -n old_set_ref=${urm_set_var_name}
new_set+=( "${!old_set_ref[@]}" )
unset -n old_set_ref
fi
new_set+=( "${old_name}" )
mvm_add "${tf_set_mvm_var_name}" "${new_name}" "${new_set[@]}"
local old
for old in "${new_set[@]}"; do
ft_map_ref["${old}"]=${new_name}
done
unset -n ft_map_ref
}
# Sets up a worktree and necessary cleanups.
#
# Params:
#
# 1 - path to the git repo
# 2 - name of a branch to be used as a base of a new worktree branch
# 3 - name of the new worktree branch
# 4 - path where the new worktree will be created
function setup_worktree() {
local repo base branch worktree_dir
repo=${1}; shift
base=${1}; shift
branch=${1}; shift
worktree_dir=${1}; shift
add_cleanup \
"git -C ${worktree_dir@Q} reset --hard HEAD" \
"git -C ${worktree_dir@Q} clean -ffdx" \
"git -C ${repo@Q} worktree remove ${worktree_dir@Q}" \
"git -C ${repo@Q} branch -D ${branch@Q}"
git -C "${repo}" worktree add -b "${branch}" "${worktree_dir}" "${base}"
}
# Creates an initial globals file. It's initial because it contains
# data known up-front, so mostly things that are defined in one place
# to avoid repeating them everywhere.
#
# More stuff will be added later to the globals file based on config
# or else.
function setup_initial_globals_file() {
local sync_script pkg_list_sort_script
sync_script="${PKG_AUTO_IMPL_DIR}/sync_with_gentoo.sh"
pkg_list_sort_script="${PKG_AUTO_IMPL_DIR}/sort_packages_list.py"
local globals_file
globals_file="${WORKDIR}/globals"
local -a sigf_arches
get_valid_arches sigf_arches
add_cleanup "rm -f ${globals_file@Q}"
cat <<EOF >"${globals_file}"
local -a GIT_ENV_VARS ARCHES WHICH REPORTS
local SDK_PKGS BOARD_PKGS
local SYNC_SCRIPT PKG_LIST_SORT_SCRIPT
GIT_ENV_VARS=(
GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL}
)
SYNC_SCRIPT=${sync_script@Q}
PKG_LIST_SORT_SCRIPT=${pkg_list_sort_script@Q}
ARCHES=( ${sigf_arches[*]@Q} )
WHICH=('old' 'new')
SDK_PKGS='sdk-pkgs'
BOARD_PKGS='board-pkgs'
REPORTS=( "\${SDK_PKGS}" "\${BOARD_PKGS}" )
EOF
}
# Extend the globals file with information from config and other
# information derived from it.
#
# Params:
#
# 1 - path to scripts repository
# 2 - path to scripts worktree with old state
# 3 - path to scripts worktree with new state
# 4 - path to reports directory
# 5 - path to aux directory
function extend_globals_file() {
local scripts old_state new_state reports_dir aux_dir
scripts=${1}; shift
old_state=${1}; shift
new_state=${1}; shift
reports_dir=${1}; shift
aux_dir=${1}; shift
local globals_file
globals_file="${WORKDIR}/globals"
if [[ ! -e "${globals_file}" ]]; then
fail 'an initial version of globals file should already exist'
fi
local old_state_branch new_state_branch
old_state_branch=$(git -C "${old_state}" rev-parse --abbrev-ref HEAD)
new_state_branch=$(git -C "${new_state}" rev-parse --abbrev-ref HEAD)
local portage_stable_suffix old_portage_stable new_portage_stable
portage_stable_suffix='sdk_container/src/third_party/portage-stable'
old_portage_stable="${old_state}/${portage_stable_suffix}"
new_portage_stable="${new_state}/${portage_stable_suffix}"
local coreos_overlay_suffix old_coreos_overlay new_coreos_overlay
coreos_overlay_suffix='sdk_container/src/third_party/coreos-overlay'
old_coreos_overlay="${old_state}/${coreos_overlay_suffix}"
new_coreos_overlay="${new_state}/${coreos_overlay_suffix}"
cat <<EOF >>"${globals_file}"
local SCRIPTS OLD_STATE NEW_STATE OLD_STATE_BRANCH NEW_STATE_BRANCH
local PORTAGE_STABLE_SUFFIX OLD_PORTAGE_STABLE NEW_PORTAGE_STABLE REPORTS_DIR
local NEW_STATE_PACKAGES_LIST AUX_DIR
local COREOS_OVERLAY_SUFFIX OLD_COREOS_OVERLAY NEW_COREOS_OVERLAY
SCRIPTS=${scripts@Q}
OLD_STATE=${old_state@Q}
NEW_STATE=${new_state@Q}
OLD_STATE_BRANCH=${old_state_branch@Q}
NEW_STATE_BRANCH=${new_state_branch@Q}
PORTAGE_STABLE_SUFFIX=${portage_stable_suffix@Q}
OLD_PORTAGE_STABLE=${old_portage_stable@Q}
NEW_PORTAGE_STABLE=${new_portage_stable@Q}
REPORTS_DIR=${reports_dir@Q}
COREOS_OVERLAY_SUFFIX=${coreos_overlay_suffix@Q}
OLD_COREOS_OVERLAY=${old_coreos_overlay@Q}
NEW_COREOS_OVERLAY=${new_coreos_overlay@Q}
NEW_STATE_PACKAGES_LIST="\${NEW_STATE}/.github/workflows/portage-stable-packages-list"
AUX_DIR=${aux_dir@Q}
EOF
# shellcheck source=for-shellcheck/globals
source "${globals_file}"
local last_nightly_version_id last_nightly_build_id
# shellcheck source=for-shellcheck/version.txt
last_nightly_version_id=$(source "${NEW_STATE}/sdk_container/.repo/manifests/version.txt"; printf '%s' "${FLATCAR_VERSION_ID}")
# shellcheck source=for-shellcheck/version.txt
last_nightly_build_id=$(source "${NEW_STATE}/sdk_container/.repo/manifests/version.txt"; printf '%s' "${FLATCAR_BUILD_ID}")
local -a locals=() definitions=()
local sdk_image_name sdk_image_var_name=SDK_IMAGE
sdk_image_name="ghcr.io/flatcar/flatcar-sdk-all:${last_nightly_version_id}-${last_nightly_build_id}"
locals+=( "${sdk_image_var_name@Q}" )
definitions+=( "${sdk_image_var_name}=${sdk_image_name@Q}" )
append_to_globals \
'' \
"local ${locals[*]}" \
'' \
"${definitions[@]}"
local -A listing_kinds
local packages_file tag filename stripped old
for arch in "${ARCHES[@]}"; do
for packages_file in "${AUX_DIR}/${arch}/"*_packages.txt; do
filename=${packages_file##*/}
stripped=${filename%_packages.txt}
case ${stripped} in
'flatcar_developer_container')
tag='dev'
;;
'flatcar_production_image')
tag='prod'
;;
'flatcar-'*)
tag="sysext-${stripped#flatcar-}"
;;
'oem-'*)
tag=${stripped#oem-}
;;
*'-flatcar')
tag="sysext-${stripped%-flatcar}"
;;
*)
devel_warn "Unknown listing file ${packages_file@Q}"
continue
;;
esac
old=${listing_kinds["${tag}"]:-}
if [[ -n ${old} ]]; then
if [[ ${old} != "${filename}" ]]; then
devel_warn "Two different packages files (${old@Q} and ${filename@Q} for a single tag ${tag@Q}"
fi
else
listing_kinds["${tag}"]=${filename}
fi
done
done
local -a sorted_tags sorted_lines
mapfile -t sorted_tags < <(printf '%s\n' "${!listing_kinds[@]}" | sort)
for tag in "${sorted_tags[@]}"; do
filename=${listing_kinds["${tag}"]}
sorted_lines+=(" [${tag@Q}]=${filename@Q}")
done
append_to_globals \
'' \
'local -A LISTING_KINDS' \
'' \
'LISTING_KINDS=(' \
"${sorted_lines[@]}" \
')'
}
# Sets up environment variables for some git commands.
#
# Make sure to call the following beforehand:
#
# local -x "${GIT_ENV_VARS[@]}"
#
# The GIT_ENV_VARS array comes from the globals file.
function setup_git_env() {
local bot_name bot_email role what
bot_name='Flatcar Buildbot'
bot_email='buildbot@flatcar-linux.org'
for role in AUTHOR COMMITTER; do
for what in name email; do
local -n var_ref="GIT_${role}_${what^^}"
local -n value_ref="bot_${what}"
var_ref=${value_ref}
unset -n value_ref
unset -n var_ref
done
done
}
# Goes over the packages list and synces them with the passed Gentoo
# repo.
#
# Params:
#
# 1 - path to the Gentoo repo
function run_sync() {
local gentoo
gentoo=${1}; shift
local -a missing_in_scripts missing_in_gentoo
missing_in_scripts=()
missing_in_gentoo=()
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -x "${GIT_ENV_VARS[@]}"
setup_git_env
local -a packages_to_update
packages_to_update=()
local package
while read -r package; do
if [[ ! -e "${NEW_PORTAGE_STABLE}/${package}" ]]; then
# If this happens, it means that the package was moved to overlay
# or dropped, the list ought to be updated.
missing_in_scripts+=("${package}")
continue
fi
if [[ ! -e "${gentoo}/${package}" ]]; then
# If this happens, it means that the package was obsoleted or moved
# in Gentoo. The obsoletion needs to be handled in the case-by-case
# manner, while move should be handled by doing the same move
# in portage-stable. The build should not break because of the move,
# because most likely it's already reflected in the profiles/updates
# directory.
missing_in_gentoo+=("${package}")
continue
fi
packages_to_update+=( "${package}" )
done < <(cat_meaningful "${NEW_STATE_PACKAGES_LIST}")
env --chdir="${NEW_PORTAGE_STABLE}" "${SYNC_SCRIPT}" -b -- "${gentoo}" "${packages_to_update[@]}"
save_missing_in_scripts "${missing_in_scripts[@]}"
save_missing_in_gentoo "${missing_in_gentoo[@]}"
}
# A helper function that prints the contents of a file skipping empty
# lines and lines starting with a hash.
#
# Params:
#
# 1 - path to a file to print
function cat_meaningful() {
local file
file=${1}; shift
xgrep '^[^#]' "${file}"
}
# Saves a list of package names to a file and adds a cleanup for
# it. The names can be loaded again with load_simple_package_list.
#
# Params:
#
# 1 - path to a file where package names will be stored
# @ - the package names
function save_simple_package_list() {
local file
file=${1}; shift
# rest are packages
add_cleanup "rm -f ${file@Q}"
if [[ ${#} -eq 0 ]]; then
truncate --size=0 "${file}"
else
printf '%s\n' "${@}" >"${file}"
fi
}
# Loads a list of packages saved previously with
# save_simple_package_list.
#
# Params:
#
# 1 - path to a file where packages were stored
# 2 - name of an array variable; will contain package names
function load_simple_package_list() {
local file packages_var_name
file=${1}; shift
packages_var_name=${1}; shift
mapfile -t "${packages_var_name}" <"${file}"
}
# General function for saving missing packages. Takes care of creating
# a directory for the listing.
#
# Params:
#
# 1 - path to a directory which will contain the listing
# 2 - name of the listing file
function save_missing_packages() {
local dir file
dir=${1}; shift
file=${1}; shift
create_cleanup_dir "${dir}"
save_simple_package_list "${dir}/${file}" "${@}"
}
# Creates a directory and adds a cleanup if the directory was missing.
#
# Params:
#
# 1 - path to the directory
function create_cleanup_dir() {
local dir
dir=${1}; shift
if [[ ! -d "${dir}" ]]; then
add_cleanup "rmdir ${dir@Q}"
mkdir "${dir}"
fi
}
# Saves a list of package names that we missing in scripts repo (which
# means that we were asked to sync a package that isn't in scripts to
# begin with).
#
# Params:
#
# @ - package names
function save_missing_in_scripts() {
save_missing_packages "${WORKDIR}/missing_in_scripts" "saved_list" "${@}"
}
# Saves a list of package names that we missing in Gentoo repo (which
# means that we were asked to sync a possibly obsolete or renamed
# package).
#
# Params:
#
# @ - package names
function save_missing_in_gentoo() {
save_missing_packages "${WORKDIR}/missing_in_gentoo" "saved_list" "${@}"
}
# Loads a list of package names that were missing in scripts repo.
#
# Params:
#
# 1 - name of an array variable; will contain package names
function load_missing_in_scripts() {
local packages_var_name
packages_var_name=${1}; shift
load_simple_package_list "${WORKDIR}/missing_in_scripts/saved_list" "${packages_var_name}"
}
# Loads a list of package names that were missing in Gentoo repo.
#
# Params:
#
# 1 - name of an array variable; will contain package names
function load_missing_in_gentoo() {
local packages_var_name
packages_var_name=${1}; shift
load_simple_package_list "${WORKDIR}/missing_in_gentoo/saved_list" "${packages_var_name}"
}
# Handles package names that were missing in scripts by dropping them
# from the listing of packages that should be updated automatically.
function handle_missing_in_scripts() {
local -a hmis_missing_in_scripts
hmis_missing_in_scripts=()
load_missing_in_scripts hmis_missing_in_scripts
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
if [[ ${#hmis_missing_in_scripts[@]} -eq 0 ]]; then
return 0;
fi
# Remove missing in scripts entries from package automation
local dir
dir="${WORKDIR}/missing_in_scripts"
create_cleanup_dir "${dir}"
local missing_re
join_by missing_re '\|' "${missing_in_scripts[@]}"
add_cleanup "rm -f ${dir@Q}/pkg_list"
xgrep --invert-match --line-regexp --fixed-strings --regexp="${missing_re}" "${NEW_STATE_PACKAGES_LIST}" >"${dir}/pkg_list"
"${PKG_LIST_SORT_SCRIPT}" "${dir}/pkg_list" >"${NEW_STATE_PACKAGES_LIST}"
local -x "${GIT_ENV_VARS[@]}"
setup_git_env
git -C "${NEW_STATE}" add "${NEW_STATE_PACKAGES_LIST}"
git -C "${NEW_STATE}" commit --quiet --message '.github: Drop missing packages from automation'
info_lines 'dropped missing packages from automation' "${missing_in_scripts[@]/#/- }"
}
# Helper function to print lines to a file clobbering the old
# contents.
#
# Params:
#
# 1 - path to the file
# @ - lines to print
function lines_to_file_truncate() {
truncate --size=0 "${1}"
lines_to_file "${@}"
}
# Helper function to append lines to a file.
#
# Params:
#
# 1 - path to the file
# @ - lines to print
function lines_to_file() {
printf '%s\n' "${@:2}" >>"${1}"
}
# Adds lines to "manual work needed" file in reports.
#
# Params:
#
# @ - lines to add
function manual() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
pkg_debug_lines 'manual work needed:' "${@}"
lines_to_file "${REPORTS_DIR}/manual-work-needed" "${@}"
}
# Adds lines to "warnings" file in reports. Should be used to report
# some issues with the processed packages.
#
# Params:
#
# @ - lines to add
function pkg_warn() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
pkg_debug_lines 'pkg warn:' "${@}"
lines_to_file "${REPORTS_DIR}/warnings" "${@}"
}
# Adds lines to "developer warnings" file in reports. Should be used
# to report some failed assumption in the automation, or bugs.
#
# Params:
#
# @ - lines to add
function devel_warn() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
pkg_debug_lines 'developer warn:' "${@}"
lines_to_file "${REPORTS_DIR}/developer-warnings" "${@}"
}
# Handles package names that were missing from Gentoo by either
# renaming and syncing them if a rename exists or by adding the
# package to the "manual work needed" file.
function handle_missing_in_gentoo() {
local gentoo
gentoo=${1}; shift
local -a hmig_missing_in_gentoo
hmig_missing_in_gentoo=()
load_missing_in_gentoo hmig_missing_in_gentoo
if [[ ${#hmig_missing_in_gentoo[@]} -eq 0 ]]; then
return 0;
fi
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -A hmig_rename_map=()
process_profile_updates_directory hmig_rename_map
local -a renamed_from renamed_to
renamed_from=()
renamed_to=()
local -x "${GIT_ENV_VARS[@]}"
setup_git_env
local missing new_name hmig_old_basename hmig_new_basename ebuild ebuild_version_ext new_ebuild_filename
for missing in "${hmig_missing_in_gentoo[@]}"; do
new_name=${hmig_rename_map["${missing}"]:-}
if [[ -z "${new_name}" ]]; then
manual "- package ${missing} is gone from Gentoo and no rename found"
continue
fi
mkdir -p "${NEW_PORTAGE_STABLE}/${new_name%/*}"
git -C "${NEW_STATE}" mv "${NEW_PORTAGE_STABLE}/${missing}" "${NEW_PORTAGE_STABLE}/${new_name}"
basename_out "${missing}" hmig_old_basename
basename_out "${new_name}" hmig_new_basename
if [[ "${hmig_old_basename}" != "${hmig_new_basename}" ]]; then
for ebuild in "${NEW_PORTAGE_STABLE}/${new_name}/${hmig_old_basename}-"*'.ebuild'; do
# 1.2.3-r4.ebuild
ebuild_version_ext=${ebuild##*/"${hmig_old_basename}-"}
new_ebuild_filename="${hmig_new_basename}-${ebuild_version_ext}"
git -C "${NEW_STATE}" mv "${ebuild}" "${NEW_PORTAGE_STABLE}/${new_name}/${new_ebuild_filename}"
done
fi
git -C "${NEW_STATE}" commit --quiet --message "${new_name}: Renamed from ${missing}"
info "renamed ${missing} to ${new_name}"
renamed_from+=("${missing}")
renamed_to+=("${new_name}")
done
if [[ ${#renamed_from[@]} -eq 0 ]]; then
return 0
fi
env --chdir="${NEW_PORTAGE_STABLE}" "${SYNC_SCRIPT}" -b -- "${gentoo}" "${renamed_to[@]}"
local dir renamed_re
dir="${WORKDIR}/missing_in_gentoo"
create_cleanup_dir "${dir}"
join_by renamed_re '\|' "${renamed_from[@]}"
add_cleanup "rm -f ${dir@Q}/pkg_list"
{
xgrep --invert-match --line-regexp --regexp="${renamed_re}" "${NEW_STATE_PACKAGES_LIST}"
printf '%s\n' "${renamed_to[@]}"
} >"${dir}/pkg_list"
"${PKG_LIST_SORT_SCRIPT}" "${dir}/pkg_list" >"${NEW_STATE_PACKAGES_LIST}"
git -C "${NEW_STATE}" add "${NEW_STATE_PACKAGES_LIST}"
git -C "${NEW_STATE}" commit --quiet --message '.github: Update package names in automation'
info 'updated packages names in automation'
}
# Process the package listings stored in the aux directory to find out
# the package tags that describe the kind of image the package is used
# in (base image, developer container, sysext image, etc.)
#
# Params:
#
# 1 - a name to an array mvm variable; will be a mapping of a package
# name to an array of tags
function process_listings() {
local pkg_to_tags_mvm_var_name
pkg_to_tags_mvm_var_name=${1}
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
#mvm_debug_enable pl_pkg_to_tags_set_mvm
mvm_declare pl_pkg_to_tags_set_mvm mvm_mvc_set
local arch kind file listing pkg
for arch in "${ARCHES[@]}"; do
for kind in "${!LISTING_KINDS[@]}"; do
file=${LISTING_KINDS["${kind}"]}
listing="${AUX_DIR}/${arch}/${file}"
if [[ ! -e "${listing}" ]]; then
# some listings are arch-specific, so they will be
# missing for other arches
continue
fi
# lines are like as follows:
#
# acct-group/adm-0-r2::portage-stable
while read -r pkg; do
pkg_debug_enable "${pkg}"
pkg_debug "processing listing ${arch}/${file}: adding tag ${kind^^}"
pkg_debug_disable
mvm_add pl_pkg_to_tags_set_mvm "${pkg}" "${kind^^}"
# VER_ERE_UNBOUNDED and PKG_ERE_UNBOUNDED come from gentoo_ver.sh
done < <(sed -E -e 's#^('"${PKG_ERE_UNBOUNDED}"')-'"${VER_ERE_UNBOUNDED}"'::.*#\1#' "${listing}")
done
done
mvm_iterate pl_pkg_to_tags_set_mvm set_mvm_to_array_mvm_cb "${pkg_to_tags_mvm_var_name}"
mvm_unset pl_pkg_to_tags_set_mvm
#mvm_debug_disable pl_pkg_to_tags_set_mvm
local -a pl_debug_pkgs pl_tags_array_name
pkg_debug_packages pl_debug_pkgs
for pkg in "${pl_debug_pkgs[@]}"; do
mvm_get "${pkg_to_tags_mvm_var_name}" "${pkg}" pl_tags_array_name
local -n tags_ref=${pl_tags_array_name:-EMPTY_ARRAY}
pkg_debug_print_c "${pkg}" "tags stored in ${pkg_to_tags_mvm_var_name}: ${tags_ref[*]}"
unset -n tags_ref
done
}
# A callback to mvm_iterate that turns a set mvm to an array mvm. It
# makes sure that the tag for the production image (or base image) is
# always first in the array.
#
# Params:
#
# 1 - name of the array mvm variable that will be filled (extra arg of
# the callback)
# 2 - name of the package
# 3 - name of the set variable holding tags
# @ - tags
function set_mvm_to_array_mvm_cb() {
local pkg_to_tags_mvm_var_name pkg
pkg_to_tags_mvm_var_name=${1}; shift
pkg=${1}; shift
local -n set_ref=${1}; shift
# rest are set items
local removed
removed=''
local -a prod_item
prod_item=()
if [[ -n ${set_ref['PROD']:-} ]]; then
prod_item+=('PROD')
unset "set_ref['PROD']"
removed=x
fi
local -a sorted_items
mapfile -t sorted_items < <(printf '%s\n' "${!set_ref[@]}" | sort)
if [[ -n ${removed} ]]; then
set_ref['PROD']=x
fi
mvm_add "${pkg_to_tags_mvm_var_name}" "${pkg}" "${prod_item[@]}" "${sorted_items[@]}"
}
# Fields of the sdk job state struct.
#
# SJS_COMMAND_IDX - an array containing a command to run
#
# SJS_STATE_DIR_IDX - run's state directory
#
# SJS_KIND_IDX - run kind (either old or new, meaning reports for
# packages before updates or after updates)
#
# SJS_JOB_NAME_IDX - job variable name
declare -gri SJS_COMMAND_IDX=0 SJS_STATE_DIR_IDX=1 SJS_KIND_IDX=2 SJS_JOB_NAME_IDX=3
# Declare SDK job state variables.
#
# Parameters:
#
# @ - names of variables to be used for states.
function sdk_job_state_declare() {
struct_declare -ga "${@}" "( 'EMPTY_ARRAY' '' '' '' )"
}
# Unset SDK job state variables.
#
# Parameters:
#
# @ - names of state variables
function sdk_job_state_unset() {
local name
for name; do
local -n sdk_job_state_ref=${name}
local array_name=${sdk_job_state_ref[SJS_COMMAND_IDX]}
if [[ ${array_name} != 'EMPTY_ARRAY' ]]; then
unset "${array_name}"
fi
unset array_name
local job_name=${sdk_job_state_ref[SJS_JOB_NAME_IDX]}
if [[ -n ${job_name} ]]; then
job_unset "${job_name}"
fi
unset job_name
unset -n sdk_run_state_ref
done
unset "${@}"
}
# Generate package reports inside SDKs for all arches and states. In
# case of failure, whatever reports where generated so far will be
# stored in salvaged-reports subdirectory of the reports directory.
# Otherwise they will end up in reports-from-sdk subdirectory.
function generate_sdk_reports() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
add_cleanup "rmdir ${WORKDIR@Q}/pkg-reports"
mkdir "${WORKDIR}/pkg-reports"
if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep --quiet --line-regexp --fixed-strings "${SDK_IMAGE}"; then
fail "No SDK image named ${SDK_IMAGE@Q} available locally, pull it before running this script"
fi
local sdk_run_kind state_var_name sdk_run_state state_branch_var_name sdk_run_state_branch
local pkg_auto_copy rv
local sdk_reports_dir top_dir dir entry full_path
local -a dir_queue all_dirs all_files
local job_args_var_name sdk_job_state_name
local -a sdk_job_state_names=()
# First create and set up SDK job states for the "before updates"
# (referred as old) and "after updates" (referred as new)
# jobs. This means creating a separate worktrees, state
# directories, and preparing commands to be run as a job.
for sdk_run_kind in "${WHICH[@]}"; do
state_var_name="${sdk_run_kind^^}_STATE"
sdk_run_state="${!state_var_name}_sdk_run"
state_branch_var_name="${sdk_run_kind^^}_STATE_BRANCH"
sdk_run_state_branch="${!state_branch_var_name}-sdk-run"
add_cleanup \
"git -C ${sdk_run_state@Q} reset --hard HEAD" \
"git -C ${sdk_run_state@Q} clean -ffdx" \
"git -C ${SCRIPTS@Q} worktree remove ${sdk_run_state@Q}" \
"git -C ${SCRIPTS@Q} branch -D ${sdk_run_state_branch@Q}"
git -C "${SCRIPTS}" \
worktree add -b "${sdk_run_state_branch}" "${sdk_run_state}" "${!state_branch_var_name}"
pkg_auto_copy=$(mktemp --tmpdir="${WORKDIR}" --directory "pkg-auto-copy.XXXXXXXX")
add_cleanup "rm -rf ${pkg_auto_copy@Q}"
cp -a "${PKG_AUTO_DIR}"/* "${pkg_auto_copy}"
gen_varname job_args_var_name
declare -ga "${job_args_var_name}=()"
local -n job_args_ref=${job_args_var_name}
job_args_ref=(
env
--chdir "${sdk_run_state}"
./run_sdk_container
-C "${SDK_IMAGE}"
-n "pkg-${sdk_run_kind}"
-U
-m "${pkg_auto_copy}:/mnt/host/source/src/scripts/pkg_auto"
--rm
./pkg_auto/inside_sdk_container.sh
pkg-reports
"${ARCHES[@]}"
)
unset -n job_args_ref
gen_varname sdk_job_state_name
sdk_job_state_declare "${sdk_job_state_name}"
local -n sdk_job_state_ref="${sdk_job_state_name}"
sdk_job_state_ref[SJS_COMMAND_IDX]=${job_args_var_name}
sdk_job_state_ref[SJS_STATE_DIR_IDX]=${sdk_run_state}
sdk_job_state_ref[SJS_KIND_IDX]=${sdk_run_kind}
unset -n sdk_job_state_ref
sdk_job_state_names+=( "${sdk_job_state_name}" )
done
# Create the jobs and kick them off.
local sdk_job_name
for sdk_job_state_name in "${sdk_job_state_names[@]}"; do
local -n sdk_job_state_ref=${sdk_job_state_name}
job_args_var_name=${sdk_job_state_ref[SJS_COMMAND_IDX]}
gen_varname sdk_job_name
job_declare "${sdk_job_name}"
all_sdk_jobs+=( "${sdk_job_name}" )
local -n job_args_ref=${job_args_var_name}
job_run -m "${sdk_job_name}" "${job_args_ref[@]}"
unset -n job_args_ref
sdk_job_state_ref[SJS_JOB_NAME_IDX]=${sdk_job_name}
unset -n sdk_job_state_ref
done
# Loop over the current running job states array to check if jobs
# in the array are still alive. The alive jobs will be added to
# the next running job states array that will be looped over (thus
# becoming the "current" array, whereas old "current" array
# becomes "next"). In the meantime gather the output from the jobs
# and print it to the terminal.
local -i current_idx=0 next_idx=1 idx state_count=${#sdk_job_state_names[@]}
local -a sdk_job_state_names_0=( "${sdk_job_state_names[@]}" ) sdk_job_state_names_1=() sdk_job_output_lines=()
local run_loop=x
while [[ state_count -gt 0 ]]; do
local -n sdk_jobs_state_names_ref=sdk_job_state_names_${current_idx}
local -n next_sdk_jobs_state_names_ref=sdk_job_state_names_${next_idx}
next_sdk_jobs_state_names_ref=()
for sdk_job_state_name in "${sdk_jobs_state_names_ref[@]}"; do
local -n sdk_job_state_ref=${sdk_job_state_name}
sdk_job_name=${sdk_job_state_ref[SJS_JOB_NAME_IDX]}
sdk_run_kind=${sdk_job_state_ref[SJS_KIND_IDX]}
unset -n sdk_job_state_ref
if job_is_alive "${sdk_job_name}"; then
next_sdk_jobs_state_names_ref+=( "${sdk_job_state_name}" )
fi
job_get_output "${sdk_job_name}" sdk_job_output_lines
if [[ ${#sdk_job_output_lines[@]} -gt 0 ]]; then
info_lines "${sdk_job_output_lines[@]/#/${sdk_run_kind}: }"
fi
done
state_count=${#next_sdk_jobs_state_names_ref[@]}
if [[ state_count -gt 0 ]]; then
sleep 0.2
fi
idx=${current_idx}
current_idx=${next_idx}
next_idx=${idx}
unset -n sdk_jobs_state_names_ref next_sdk_jobs_state_names_ref
done
# All jobs are done now, so reap them and check if the
# succeeded. If they failed, print the contents of the warning
# files and save the reports they have generated so far in a
# separate place.
local sr_dir_created='' gsr_sdk_run_state_basename
for sdk_job_state_name in "${sdk_job_state_names[@]}"; do
local -n sdk_job_state_ref=${sdk_job_state_name}
sdk_job_name=${sdk_job_state_ref[SJS_JOB_NAME_IDX]}
sdk_run_state=${sdk_job_state_ref[SJS_STATE_DIR_IDX]}
sdk_run_kind=${sdk_job_state_ref[SJS_KIND_IDX]}
unset -n sdk_job_state_ref
job_reap "${sdk_job_name}" rv
if [[ ${rv} -ne 0 ]]; then
# Report generation failed, save the generated reports in
# REPORTS_DIR for further examination by the developer.
local salvaged_dir salvaged_dir_sdk
salvaged_dir="${REPORTS_DIR}/salvaged-reports"
basename_out "${sdk_run_state}" gsr_sdk_run_state_basename
salvaged_dir_sdk="${salvaged_dir}/${gsr_sdk_run_state_basename}"
if [[ -z ${sr_dir_created} ]]; then
rm -rf "${salvaged_dir}"
mkdir -p "${salvaged_dir}"
sr_dir_created=x
fi
{
info "run_sdk_container for ${sdk_run_kind@Q} finished with exit status ${rv}, printing the warnings below for a clue"
info
for file in "${sdk_run_state}/pkg-reports/"*'-warnings'; do
info "from ${file}:"
echo
cat "${file}"
echo
done
info
info 'whatever reports generated by the failed run are saved in'
info "${salvaged_dir_sdk@Q} directory"
info
} >&2
cp -a "${sdk_run_state}/pkg-reports" "${salvaged_dir_sdk}"
unset salvaged_dir salvaged_dir_sdk
else
# We succeeded, so move the reports from the state dir to
# workdir and generate cleanups for the moved reports.
sdk_reports_dir="${WORKDIR}/pkg-reports/${sdk_run_kind}"
top_dir="${sdk_run_state}/pkg-reports"
dir_queue=( "${top_dir}" )
all_dirs=()
all_files=()
while [[ ${#dir_queue[@]} -gt 0 ]]; do
dir=${dir_queue[0]}
dir_queue=( "${dir_queue[@]:1}" )
entry=${dir#"${top_dir}"}
if [[ -z ${entry} ]]; then
all_dirs=( "${sdk_reports_dir}" "${all_dirs[@]}" )
else
entry=${entry#/}
all_dirs=( "${sdk_reports_dir}/${entry}" "${all_dirs[@]}" )
fi
for full_path in "${dir}/"*; do
if [[ -d ${full_path} ]]; then
dir_queue+=( "${full_path}" )
else
entry=${full_path##"${top_dir}/"}
all_files+=( "${sdk_reports_dir}/${entry}" )
fi
done
done
add_cleanup \
"rm -f ${all_files[*]@Q}" \
"rmdir ${all_dirs[*]@Q}"
mv "${sdk_run_state}/pkg-reports" "${sdk_reports_dir}"
fi
done
sdk_job_state_unset "${sdk_job_state_names[@]}"
# salvaged reports directory was created, means that report
# generation failed
if [[ -n ${sr_dir_created} ]]; then
fail "copying done, stopping now"
fi
cp -a "${WORKDIR}/pkg-reports" "${REPORTS_DIR}/reports-from-sdk"
}
source "${PKG_AUTO_IMPL_DIR}/mvm.sh"
# pkginfo mvm is a map mvm that has the following Go-like type:
#
# map[pkg]map[slot]version
#
# pkg, slot and version are strings
# Generate a name for pkginfo mvm based on passed information.
#
# Params:
#
# 1 - which state it refers to (old or new)
# 2 - architecture
# 3 - which report (board packages or SDK packages)
# 4 - name of a variable that will contain the name
function pkginfo_name() {
local which arch report
which=${1}; shift
arch=${1}; shift
report=${1}; shift
local -n pi_name_ref=${1}; shift
pi_name_ref="pkginfo_${which}_${arch}_${report//-/_}_pimap_mvm"
}
# Constructor callback used by mvm_declare for pkginfo mvms.
function pkginfo_constructor() {
mvm_mvc_map_constructor "${@}"
}
# Destructor callback used by mvm_declare for pkginfo mvms.
function pkginfo_destructor() {
mvm_mvc_map_destructor "${@}"
}
# Adder callback used by mvm_declare for pkginfo mvms.
function pkginfo_adder() {
local -n map_ref=${1}; shift
local mark
while [[ ${#} -gt 1 ]]; do
mark=${map_ref["${1}"]:-}
if [[ -n "${mark}" ]]; then
fail "multiple versions for a single slot for a package in a single report"
fi
map_ref["${1}"]=${2}
shift 2
done
}
# Creates a pkginfo mvm.
#
# Params:
#
# 1 - which state it refers to (old or new)
# 2 - architecture
# 3 - which report (board packages or SDK packages)
# 4 - name of a variable that will contain the name of the created
# pkginfo mvm
function pkginfo_declare() {
local which arch report pi_name_var_name
which=${1}; shift
arch=${1}; shift
report=${1}; shift
pi_name_var_name=${1}; shift
pkginfo_name "${which}" "${arch}" "${report}" "${pi_name_var_name}"
local -a extras
extras=(
'which' "${which}"
'arch' "${arch}"
'report' "${report}"
)
mvm_declare "${!pi_name_var_name}" pkginfo -- "${extras[@]}"
}
# Destroys a pkginfo mvm.
#
# Params:
#
# 1 - which state it refers to (old or new)
# 2 - architecture
# 3 - which report (board packages or SDK packages)
function pkginfo_unset() {
local which arch report
which=${1}; shift
arch=${1}; shift
report=${1}; shift
local piu_pi_name
pkginfo_name "${which}" "${arch}" "${report}" piu_pi_name
mvm_unset "${piu_pi_name}"
}
# Processes the report file associated to the passed pkginfo mvm. The
# pkginfo mvm is filled with info about packages, slots and
# versions. Additional information is put into passed package set and
# package to slots set mvm.
#
# Params:
#
# 1 - name of the pkginfo mvm variable
# 2 - name of the set variable, will contain packages in the report
# 3 - name of the set mvm variable, will contain a map of package to
# slots
function pkginfo_process_file() {
mvm_call "${1}" pkginfo_c_process_file "${@:2}"
}
# Helper function for pkginfo_process_file, used by mvm_call.
function pkginfo_c_process_file() {
local pkg_slots_set_mvm_var_name
local -n pkg_set_ref=${1}; shift
pkg_slots_set_mvm_var_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local which arch report
mvm_c_get_extra 'which' which
mvm_c_get_extra 'arch' arch
mvm_c_get_extra 'report' report
local report_file
case ${report}:${arch} in
"${SDK_PKGS}:arm64")
# short-circuit it, there's no arm64 sdk
return 0
;;
"${SDK_PKGS}:amd64")
report_file="${WORKDIR}/pkg-reports/${which}/${report}"
;;
"${BOARD_PKGS}:"*)
report_file="${WORKDIR}/pkg-reports/${which}/${arch}-${report}"
;;
*)
local c=${report}:${arch}
devel_warn "unknown report-architecture combination (${c@Q})"
return 0
esac
local pkg version_slot throw_away v s
while read -r pkg version_slot throw_away; do
pkg_debug_enable "${pkg}"
pkg_debug "${which} ${arch} ${report}: ${version_slot}"
v=${version_slot%%:*}
s=${version_slot##*:}
mvm_c_add "${pkg}" "${s}" "${v}"
pkg_set_ref["${pkg}"]='x'
mvm_add "${pkg_slots_set_mvm_var_name}" "${pkg}" "${s}"
pkg_debug_disable
done <"${report_file}"
}
# Gets a profile of the pkginfo mvm. The "profile" is a confusing
# misnomer as it has nothing to do with Gentoo profiles, but rather a
# description of the pkginfo (which is a which-arch-report triplet)
# that is used for reporting.
function pkginfo_profile() {
mvm_call "${1}" pkginfo_c_profile "${@:2}"
}
# Helper function for pkginfo_profile, used by mvm_call.
function pkginfo_c_profile() {
local profile_var_name
profile_var_name=${1}; shift
local which arch report
mvm_c_get_extra 'which' which
mvm_c_get_extra 'arch' arch
mvm_c_get_extra 'report' report
printf -v "${profile_var_name}" '%s-%s-%s' "${which}" "${arch}" "${report}"
}
# Creates pkginfo maps for all the reports and processes the
# reports. Additional information is stored in passed packages array
# and packages to slots set mvm variables.
#
# Params:
#
# 1 - name of the array variable, will contain sorted packages from
# all the reports
# 2 - name of the set mvm variable, will contain a map of package to
# slots
function read_reports() {
local all_pkgs_var_name pkg_slots_set_mvm_var_name
all_pkgs_var_name=${1}; shift
pkg_slots_set_mvm_var_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -A rr_all_packages_set
rr_all_packages_set=()
local arch which report rr_pimap_mvm_var_name
for arch in "${ARCHES[@]}"; do
for which in "${WHICH[@]}"; do
for report in "${REPORTS[@]}"; do
pkginfo_declare "${which}" "${arch}" "${report}" rr_pimap_mvm_var_name
pkginfo_process_file "${rr_pimap_mvm_var_name}" rr_all_packages_set "${pkg_slots_set_mvm_var_name}"
done
done
done
mapfile -t "${all_pkgs_var_name}" < <(printf '%s\n' "${!rr_all_packages_set[@]}" | sort)
}
# Destroys the pkginfo maps for all the reports.
function unset_report_mvms() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local arch which report
for arch in "${ARCHES[@]}"; do
for which in "${WHICH[@]}"; do
for report in "${REPORTS[@]}"; do
pkginfo_unset "${which}" "${arch}" "${report}"
done
done
done
}
# Finds out the highest and the lowest version from the passed versions.
#
# Params:
#
# 1 - name of a variable where the min version will be stored
# 2 - name of a variable where the min version will be stored
# @ - the versions
function ver_min_max() {
local -n min_ref=${1}; shift
local -n max_ref=${1}; shift
local min max v
min=''
max=''
for v; do
if [[ -z ${min} ]] || ver_test "${v}" -lt "${min}"; then
min=${v}
fi
if [[ -z ${max} ]] || ver_test "${v}" -gt "${max}"; then
max=${v}
fi
done
min_ref=${min}
max_ref=${max}
}
# Does consistency checks on two profiles for a package using an
# additional map for slots information for a package. Checks if common
# slots for the package in both profiles are using the same
# versions. This is to catch version discrepancies that sometimes
# happen when e.g. a package gets stabilized for one arch, but not for
# the other.
#
# While at it, store package, slot and version range information into
# the passed map.
#
# 1 - package
# 2 - name of the pkginfo mvm for profile 1
# 3 - name of the pkginfo mvm for profile 2
# 4 - name of the pkg to slots to version range map mvm # TODO: This should be the last parameter
# 5 - name of the pkg to all slots set mvm
function consistency_check_for_package() {
local pkg pi1_pimap_mvm_var_name pi2_pimap_mvm_var_name pkg_slot_verminmax_map_mvm_var_name pkg_slots_set_mvm_var_name
pkg=${1}; shift
pi1_pimap_mvm_var_name=${1}; shift
pi2_pimap_mvm_var_name=${1}; shift
pkg_slot_verminmax_map_mvm_var_name=${1}; shift
pkg_slots_set_mvm_var_name=${1}; shift
local ccfp_slot_version1_map_var_name ccfp_slot_version2_map_var_name
mvm_get "${pi1_pimap_mvm_var_name}" "${pkg}" ccfp_slot_version1_map_var_name
mvm_get "${pi2_pimap_mvm_var_name}" "${pkg}" ccfp_slot_version2_map_var_name
local -A empty_map
empty_map=()
local -n slot_version1_map_ref=${ccfp_slot_version1_map_var_name:-empty_map}
local -n slot_version2_map_ref=${ccfp_slot_version2_map_var_name:-empty_map}
local ccfp_slots_set_var_name
mvm_get "${pkg_slots_set_mvm_var_name}" "${pkg}" ccfp_slots_set_var_name
local -n slots_set_ref=${ccfp_slots_set_var_name}
local -a profile_1_slots profile_2_slots common_slots
profile_1_slots=()
profile_2_slots=()
common_slots=()
local ccfp_profile_1 ccfp_profile_2
pkginfo_profile "${pi1_pimap_mvm_var_name}" ccfp_profile_1
pkginfo_profile "${pi2_pimap_mvm_var_name}" ccfp_profile_2
local s v1 v2 ccfp_min ccfp_max mm
pkg_debug "all slots iterated over: ${!slots_set_ref[*]}"
for s in "${!slots_set_ref[@]}"; do
v1=${slot_version1_map_ref["${s}"]:-}
v2=${slot_version2_map_ref["${s}"]:-}
pkg_debug "v1: ${v1}, v2: ${v2}"
if [[ -n ${v1} ]] && [[ -n ${v2} ]]; then
pkg_debug "${s} is a common slot for ${ccfp_profile_1} and ${ccfp_profile_2}"
common_slots+=( "${s}" )
if [[ ${v1} != "${v2}" ]]; then
pkg_warn \
"- version mismatch:" \
" - package: ${pkg}" \
" - slot: ${s}" \
" - profile 1: ${ccfp_profile_1}" \
" - version: ${v1}" \
" - profile 1: ${ccfp_profile_2}" \
" - version: ${v2}"
fi
ver_min_max ccfp_min ccfp_max "${v1}" "${v2}"
mm="${ccfp_min}:${ccfp_max}"
elif [[ -n ${v1} ]]; then
# only side1 has the slot
pkg_debug "${s} is a slot only in ${ccfp_profile_1}"
profile_1_slots+=( "${s}" )
mm="${v1}:${v1}"
elif [[ -n ${v2} ]]; then
# only side 2 has the slot
pkg_debug "${s} is a slot only in ${ccfp_profile_2}"
profile_2_slots+=( "${s}" )
mm="${v2}:${v2}"
else
pkg_debug "${s} is a slot absent from both ${ccfp_profile_1} and ${ccfp_profile_2}"
continue
fi
mvm_add "${pkg_slot_verminmax_map_mvm_var_name}" "${pkg}" "${s}" "${mm}"
done
pkg_debug "common slots: ${common_slots[*]}"
pkg_debug "profile 1 slots: ${profile_1_slots[*]}"
pkg_debug "profile 2 slots: ${profile_2_slots[*]}"
local s1 s2
if [[ ${#common_slots[@]} -gt 0 ]]; then
if [[ ${#profile_1_slots[@]} -gt 0 ]] || [[ ${#profile_2_slots[@]} -gt 0 ]]; then
pkg_warn \
"- suspicious:" \
" - package: ${pkg}" \
" - profile 1: ${ccfp_profile_1}" \
" - profile 2: ${ccfp_profile_2}" \
" - common slots: ${common_slots[*]}" \
" - slots only in profile 1: ${profile_1_slots[*]}" \
" - slots only in profile 2: ${profile_2_slots[*]}" \
" - what: there are slots that exist only on one profile while both profiles also have some common slots"
fi
elif [[ ${#profile_1_slots[@]} -eq 1 ]] && [[ ${#profile_2_slots[@]} -eq 1 ]]; then
s1=${profile_1_slots[0]}
s2=${profile_2_slots[0]}
v1=${slot_version1_map_ref["${s1}"]:-}
v2=${slot_version2_map_ref["${s2}"]:-}
if [[ ${v1} != "${v2}" ]]; then
pkg_warn \
"- version mismatch:" \
" - package ${pkg}" \
" - profile 1: ${ccfp_profile_1}" \
" - slot: ${profile_1_slots[0]}" \
" - version: ${v1}" \
" - profile 2: ${ccfp_profile_2}" \
" - slot: ${profile_2_slots[0]}" \
" - version: ${v2}"
fi
fi
}
# Do consistency checks for the following pairs of profiles for a passed state:
#
# ${arch} sdk <-> ${arch} board
# ${arch1} board <-> ${arch2} board
# ${arch1} sdk <-> ${arch2} sdk
#
# While at it, store package, slot and version range information into
# the passed map.
#
# Params:
#
# 1 - which state should be checked (old or new)
# 2 - name of an array variable that contains all the package names
# 3 - name of the pkg to all slots set mvm variable
# 4 - name of the pkg to slot to version range map mvm variable
function consistency_checks() {
local which pkg_slots_set_mvm_var_name pkg_slot_verminmax_mvm_var_name
which=${1}; shift
local -n all_pkgs_ref=${1}; shift
pkg_slots_set_mvm_var_name=${1}; shift
pkg_slot_verminmax_mvm_var_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local cc_pimap_mvm_1_var_name cc_pimap_mvm_2_var_name pkg
local -a all_mvm_names=()
local arch name
# ${arch} SDK <-> ${arch} board
for arch in "${ARCHES[@]}"; do
# currently we only have amd64 SDK, so skip others
if [[ ${arch} != 'amd64' ]]; then
continue
fi
pkginfo_name "${which}" "${arch}" "${SDK_PKGS}" cc_pimap_mvm_1_var_name
pkginfo_name "${which}" "${arch}" "${BOARD_PKGS}" cc_pimap_mvm_2_var_name
name="cc_${arch}_sdk_board_pkg_slot_verminmax_map_mvm"
all_mvm_names+=( "${name}" )
mvm_declare "${name}" mvm_mvc_map
for pkg in "${all_pkgs_ref[@]}"; do
pkg_debug_enable "${pkg}"
pkg_debug "${which} ${arch} sdk <-> ${arch} board"
consistency_check_for_package "${pkg}" "${cc_pimap_mvm_1_var_name}" "${cc_pimap_mvm_2_var_name}" "${name}" "${pkg_slots_set_mvm_var_name}"
pkg_debug_disable
done
done
# We want to check consistency between each pair of arches.
local -a cc_all_arch_pairs
all_pairs cc_all_arch_pairs ':' "${ARCHES[@]}"
local pair arch1 arch2
# ${arch1} board <-> ${arch2} board
for pair in "${cc_all_arch_pairs[@]}"; do
arch1=${pair%:*}
arch2=${pair#*:}
pkginfo_name "${which}" "${arch1}" "${BOARD_PKGS}" cc_pimap_mvm_1_var_name
pkginfo_name "${which}" "${arch2}" "${BOARD_PKGS}" cc_pimap_mvm_2_var_name
name="cc_${arch1}_${arch2}_board_pkg_slot_verminmax_map_mvm"
all_mvm_names+=( "${name}" )
mvm_declare "${name}" mvm_mvc_map
for pkg in "${all_pkgs_ref[@]}"; do
pkg_debug_enable "${pkg}"
pkg_debug "${which} ${arch1} board <-> ${arch2} board"
consistency_check_for_package "${pkg}" "${cc_pimap_mvm_1_var_name}" "${cc_pimap_mvm_2_var_name}" "${name}" "${pkg_slots_set_mvm_var_name}"
pkg_debug_disable
done
done
# ${arch1} sdk <-> ${arch2} sdk
for pair in "${cc_all_arch_pairs[@]}"; do
arch1=${pair%:*}
arch2=${pair#*:}
# We currently only have amd64 SDK, so this loop will
# effectively iterate zero times. When we get the arm64 SDK
# too, this if could be dropped. Getting the listing of arm64
# packages inside amd64 SDK is going to be problem to solve,
# though.
if [[ ${arch1} != 'amd64' ]] || [[ ${arch2} != 'amd64' ]]; then
continue
fi
pkginfo_name "${which}" "${arch1}" "${SDK_PKGS}" cc_pimap_mvm_1_var_name
pkginfo_name "${which}" "${arch2}" "${SDK_PKGS}" cc_pimap_mvm_2_var_name
name="cc_${arch1}_${arch2}_sdk_pkg_slot_verminmax_map_mvm"
all_mvm_names+=( "${name}" )
mvm_declare "${name}" mvm_mvc_map
for pkg in "${all_pkgs_ref[@]}"; do
pkg_debug_enable "${pkg}"
pkg_debug "${which} ${arch1} sdk <-> ${arch2} sdk"
consistency_check_for_package "${pkg}" "${cc_pimap_mvm_1_var_name}" "${cc_pimap_mvm_2_var_name}" "${name}" "${pkg_slots_set_mvm_var_name}"
pkg_debug_disable
done
done
local cc_slots_set_var_name s cc_min cc_max verminmax
local -A empty_map=()
local -a verminmax_map_var_names verminmaxes
local cc_slot_verminmax_map_var_name
for pkg in "${all_pkgs_ref[@]}"; do
pkg_debug_enable "${pkg}"
pkg_debug "${which} verminmax stuff"
verminmax_map_var_names=()
for name in "${all_mvm_names[@]}"; do
mvm_get "${name}" "${pkg}" cc_slot_verminmax_map_var_name
verminmax_map_var_names+=("${cc_slot_verminmax_map_var_name}")
done
if pkg_debug_enabled; then
for name in "${verminmax_map_var_names[@]}"; do
local -n slot_verminmax_map_ref=${name:-empty_map}
pkg_debug_print "all slots in ${name}: ${!slot_verminmax_map_ref[*]}"
pkg_debug_print "all vmms in ${name}: ${slot_verminmax_map_ref[*]}"
unset -n slot_verminmax_map_ref
done
fi
mvm_get "${pkg_slots_set_mvm_var_name}" "${pkg}" cc_slots_set_var_name
local -n slots_set_ref=${cc_slots_set_var_name}
pkg_debug "all slots iterated over: ${!slots_set_ref[*]}"
for s in "${!slots_set_ref[@]}"; do
verminmaxes=()
for name in "${verminmax_map_var_names[@]}"; do
local -n slot_verminmax_map_ref=${name:-empty_map}
verminmax=${slot_verminmax_map_ref["${s}"]:-}
if [[ -n ${verminmax} ]]; then
verminmaxes+=( "${verminmax}" )
fi
unset -n slot_verminmax_map_ref
done
if [[ ${#verminmaxes[@]} -gt 1 ]]; then
ver_min_max cc_min cc_max "${verminmaxes[@]%%:*}" "${verminmaxes[@]##*:}"
verminmax="${cc_min}:${cc_max}"
elif [[ ${#verminmaxes[@]} -eq 1 ]]; then
verminmax=${verminmaxes[0]}
else
continue
fi
pkg_debug "adding vmm ${verminmax} for slot ${s}"
mvm_add "${pkg_slot_verminmax_mvm_var_name}" "${pkg}" "${s}" "${verminmax}"
done
unset -n slots_set_ref
pkg_debug_disable
done
for name in "${all_mvm_names[@]}"; do
mvm_unset "${name}"
done
}
# Read a report describing from which repo the package came and store
# in the passed map.
#
# Params:
#
# 1 - name of a map variable, will contain a mapping of package name
# to repository name
function read_package_sources() {
local -n package_sources_map_ref=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a files=()
local which arch
for which in "${WHICH[@]}"; do
files+=( "${WORKDIR}/pkg-reports/${which}/sdk-package-repos" )
for arch in "${ARCHES[@]}"; do
files+=( "${WORKDIR}/pkg-reports/${which}/${arch}-board-package-repos" )
done
done
local file pkg repo saved_repo
for file in "${files[@]}"; do
while read -r pkg repo; do
saved_repo=${package_sources_map_ref["${pkg}"]:-}
if [[ -n ${saved_repo} ]]; then
if [[ ${saved_repo} != "${repo}" ]]; then
pkg_warn \
'- different repos used for the package:' \
" - package: ${pkg}" \
' - repos:' \
" - ${saved_repo}" \
" - ${repo}"
fi
else
package_sources_map_ref["${pkg}"]=${repo}
fi
done <"${file}"
done
}
# This monstrosity takes renames map and package tags information,
# reads the reports, does consistency checks and uses the information
# from previous steps to write out package differences between the old
# and new state into the reports directory.
#
# Params:
#
# 1 - name of the renames map variable
# 2 - name of the package tags map mvm variable
function handle_package_changes() {
local pkg_to_tags_mvm_var_name
local -n renamed_old_to_new_map_ref=${1}; shift
pkg_to_tags_mvm_var_name=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a hpc_all_pkgs
hpc_all_pkgs=()
# map[package]map[slot]interface{}
mvm_declare hpc_pkg_slots_set_mvm mvm_mvc_set
read_reports hpc_all_pkgs hpc_pkg_slots_set_mvm
# map[package]map[slot]string (string being "min version:max version")
mvm_declare hpc_old_pkg_slot_verminmax_map_mvm mvm_mvc_map
mvm_declare hpc_new_pkg_slot_verminmax_map_mvm mvm_mvc_map
consistency_checks old hpc_all_pkgs hpc_pkg_slots_set_mvm hpc_old_pkg_slot_verminmax_map_mvm
consistency_checks new hpc_all_pkgs hpc_pkg_slots_set_mvm hpc_new_pkg_slot_verminmax_map_mvm
unset_report_mvms
# TODO: when we handle moving packages between repos, then there
# should be two maps, for old and new state
local -A hpc_package_sources_map
hpc_package_sources_map=()
read_package_sources hpc_package_sources_map
mkdir -p "${REPORTS_DIR}/updates"
local -a old_pkgs new_pkgs
old_pkgs=()
new_pkgs=()
# The following loop fills the old_pkgs and new_pkgs arrays sorted
# package names, where old package name at index I has it's new
# counterpart at the same index. For the most part, both old and
# new names will be the same, since the renames are rather rare.
# map[package]index
local -A added_pkg_to_index_map=()
local pkg other
for pkg in "${hpc_all_pkgs[@]}"; do
other=${renamed_old_to_new_map_ref["${pkg}"]:-}
if [[ -n "${other}" ]]; then
# There seem be a rename from ${pkg} to ${other}
pkg_debug_enable "${pkg}" "${other}"
pkg_debug "${pkg} renamed to ${other}"
pkg_debug_disable
local other_idx
other_idx=${added_pkg_to_index_map["${other}"]:-}
if [[ -n ${other_idx} ]]; then
# Looks like we have already processed the ${other}
# name. In this case, both old_pkgs[${other_idx}] and
# new_pkgs[${other_idx}] should just be
# ${other}. Since ${pkg} is the old name (${other} is
# new), we update old_pkgs to hold the old name. Just
# make sure that old_pkgs indeed had the new name
# first.
local other_old
other_old=${old_pkgs["${other_idx}"]}
if [[ ${other_old} = "${other}" ]]; then
old_pkgs["${other_idx}"]=${pkg}
else
manual \
'- there seem to be two old packages in our repos that are supposed to be renamed to the same name:' \
" - old package 1: ${pkg}" \
" - old package 2: ${other_old}" \
" - new package: ${other}"
fi
unset other_idx other_old
continue
else
unset other_idx
fi
# Looks like we haven't processed the ${other} name yet,
# it probably will come up later, which will be taken care
# of by the "pkg_debug 'handled already through some
# rename'" part below, after else.
local pkg_idx
# doesn't matter if it's length of new_pkgs or old_pkgs,
# both are assumed to have the same length
pkg_idx=${#old_pkgs[@]}
old_pkgs+=("${pkg}")
new_pkgs+=("${other}")
added_pkg_to_index_map["${pkg}"]=${pkg_idx}
added_pkg_to_index_map["${other}"]=${pkg_idx}
unset pkg_idx
else
pkg_debug_enable "${pkg}"
if [[ -n ${added_pkg_to_index_map["${pkg}"]:-} ]]; then
pkg_debug 'handled already through some rename'
else
pkg_debug "${pkg} is not renamed"
local pkg_idx
# doesn't matter if it's length of new_pkgs or old_pkgs,
# both are assumed to have the same length
pkg_idx=${#old_pkgs[@]}
old_pkgs+=("${pkg}")
new_pkgs+=("${pkg}")
added_pkg_to_index_map["${pkg}"]=${pkg_idx}
fi
pkg_debug_disable
fi
done
unset added_pkg_to_index_map
# The loop below goes over the pairs of old and new package
# names. For each name there will be some checks done (like does
# this package even exist). Each name in the pair has a set of
# used slots associated with it (the most common situation is that
# each have just one slot, but there are some packages that we
# have multiple slots installed, like
# app-text/docbook-xml-dtd). Some of the slots will appear in both
# old and new package name, sometimes there will be slots
# available only in the old state or only in the new state. Each
# slot for each package name has an associated min version and max
# version. So for common slots we usually compare min version for
# old package with max version for new package. Any
# inconsistencies with the versions should be reported by
# now. There are some edge cases with the slots that are not
# handled by the automation - in such cases there will be a
# "manual action needed" report.
local pkg_idx=0
local old_name new_name old_repo new_repo
local hpc_old_slots_set_var_name hpc_new_slots_set_var_name
local hpc_old_slot_verminmax_map_var_name hpc_new_slot_verminmax_map_var_name
local s hpc_old_s hpc_new_s
local old_verminmax new_verminmax
local old_version new_version
local hpc_cmp_result
local -A hpc_only_old_slots_set hpc_only_new_slots_set hpc_common_slots_set
local -a lines
local hpc_update_dir
local -A empty_map_or_set
local hpc_changed hpc_slot_changed hpc_update_dir_non_slot hpc_category_dir
local which slots_set_var_name_var_name slot_verminmax_map_var_name_var_name filtered_slots_set_var_name verminmax
local -A hpc_old_filtered_slots_set hpc_new_filtered_slots_set
empty_map_or_set=()
while [[ ${pkg_idx} -lt ${#old_pkgs[@]} ]]; do
old_name=${old_pkgs["${pkg_idx}"]}
new_name=${new_pkgs["${pkg_idx}"]}
if [[ ${old_name} = "${new_name}" ]]; then
info "handling update of ${new_name}"
else
info "handling update of ${new_name} (renamed from ${old_name})"
fi
pkg_debug_enable "${old_name}" "${new_name}"
pkg_debug 'handling updates'
pkg_idx=$((pkg_idx + 1))
old_repo=${hpc_package_sources_map["${old_name}"]:-}
new_repo=${hpc_package_sources_map["${new_name}"]:-}
if [[ -z ${old_repo} ]]; then
pkg_warn \
'- package not in old state' \
" - old package: ${old_name}" \
" - new package: ${new_name}"
pkg_debug_disable
continue
fi
if [[ -z ${new_repo} ]]; then
pkg_warn \
'- package not in new state' \
" - old package: ${old_name}" \
" - new package: ${new_name}"
pkg_debug_disable
continue
fi
if [[ ${old_repo} != "${new_repo}" ]]; then
# This is pretty much an arbitrary limitation and I don't
# remember any more why we have it.
pkg_warn \
'- package has moved between repos? unsupported for now' \
" - old package and repo: ${old_name} ${old_repo}" \
" - new package and repo: ${new_name} ${new_repo}"
pkg_debug_disable
continue
fi
if [[ ${new_repo} != 'portage-stable' ]]; then
# coreos-overlay packages will need a separate handling
pkg_debug 'not a portage-stable package'
pkg_debug_disable
continue
fi
mvm_get hpc_pkg_slots_set_mvm "${old_name}" hpc_old_slots_set_var_name
mvm_get hpc_pkg_slots_set_mvm "${new_name}" hpc_new_slots_set_var_name
: "${hpc_old_slots_set_var_name:=empty_map_or_set}"
: "${hpc_new_slots_set_var_name:=empty_map_or_set}"
mvm_get hpc_old_pkg_slot_verminmax_map_mvm "${old_name}" hpc_old_slot_verminmax_map_var_name
mvm_get hpc_new_pkg_slot_verminmax_map_mvm "${new_name}" hpc_new_slot_verminmax_map_var_name
: "${hpc_old_slot_verminmax_map_var_name:=empty_map_or_set}"
: "${hpc_new_slot_verminmax_map_var_name:=empty_map_or_set}"
local -n old_slot_verminmax_map_ref=${hpc_old_slot_verminmax_map_var_name}
local -n new_slot_verminmax_map_ref=${hpc_new_slot_verminmax_map_var_name}
# Filter out slots for old and new package name that comes out
# without versions. This may happen, because we collect all
# slot names for the package name, without differentiating
# whether such a slot existed in the old state or still exists
# in the new state. If slot didn't exist in either one then it
# will come without version information. Such a slot is
# dropped. An example would be an update of sys-devel/binutils
# from 2.42 to 2.43. Each binutils version has a separate slot
# which is named after the version. So the slots set would be
# (2.42 2.43). Slot "2.42" does not exist in the new state any
# more, "2.43" does not yet exist in the old state. So those
# slots for those states will be dropped. Thus filtered slots
# set for the old state will only contain 2.42, while for the
# new state - only 2.43.
for which in old new; do
slots_set_var_name_var_name="hpc_${which}_slots_set_var_name"
slot_verminmax_map_var_name_var_name="hpc_${which}_slot_verminmax_map_var_name"
filtered_slots_set_var_name="hpc_${which}_filtered_slots_set"
local -n which_slots_set_ref=${!slots_set_var_name_var_name}
local -n which_slot_verminmax_map_ref=${!slot_verminmax_map_var_name_var_name}
local -n which_filtered_slots_set_ref=${filtered_slots_set_var_name}
pkg_debug "all unfiltered slots for ${which} name: ${!which_slots_set_ref[*]}"
which_filtered_slots_set_ref=()
for s in "${!which_slots_set_ref[@]}"; do
verminmax=${which_slot_verminmax_map_ref["${s}"]:-}
if [[ -n ${verminmax} ]]; then
which_filtered_slots_set_ref["${s}"]=x
fi
done
pkg_debug "all filtered slots for ${which} name: ${!which_filtered_slots_set_ref[*]}"
unset -n which_filtered_slots_set_ref
unset -n which_slot_verminmax_map_ref
unset -n which_slots_set_ref
done
hpc_only_old_slots_set=()
hpc_only_new_slots_set=()
hpc_common_slots_set=()
sets_split \
hpc_old_filtered_slots_set hpc_new_filtered_slots_set \
hpc_only_old_slots_set hpc_only_new_slots_set hpc_common_slots_set
pkg_debug "all common slots: ${!hpc_common_slots_set[*]}"
pkg_debug "slots only for old name: ${!hpc_only_old_slots_set[*]}"
pkg_debug "slots only for new name: ${!hpc_only_new_slots_set[*]}"
update_dir_non_slot "${new_name}" hpc_update_dir_non_slot
mkdir -p "${hpc_update_dir_non_slot}"
generate_non_ebuild_diffs "${OLD_PORTAGE_STABLE}" "${NEW_PORTAGE_STABLE}" "${old_name}" "${new_name}"
generate_full_diffs "${OLD_PORTAGE_STABLE}" "${NEW_PORTAGE_STABLE}" "${old_name}" "${new_name}"
generate_package_mention_reports "${NEW_STATE}" "${old_name}" "${new_name}"
hpc_changed=
pkg_debug 'going over common slots'
for s in "${!hpc_common_slots_set[@]}"; do
old_verminmax=${old_slot_verminmax_map_ref["${s}"]:-}
new_verminmax=${new_slot_verminmax_map_ref["${s}"]:-}
pkg_debug "slot: ${s}, vmm old: ${old_verminmax}, vmm new: ${new_verminmax}"
if [[ -z "${old_verminmax}" ]] || [[ -z "${new_verminmax}" ]]; then
devel_warn \
"- no minmax info available for old and/or new:" \
" - old package: ${old_name}" \
" - slot: ${s}" \
" - minmax: ${old_verminmax}" \
" - new package: ${new_name}" \
" - slot: ${s}" \
" - minmax: ${new_verminmax}"
continue
fi
update_dir "${new_name}" "${s}" "${s}" hpc_update_dir
mkdir -p "${hpc_update_dir}"
old_version=${old_verminmax%%:*}
new_version=${new_verminmax##*:}
gentoo_ver_cmp_out "${new_version}" "${old_version}" hpc_cmp_result
case ${hpc_cmp_result} in
"${GV_GT}")
handle_pkg_update "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${s}" "${s}" "${old_version}" "${new_version}"
hpc_changed=x
;;
"${GV_EQ}")
hpc_slot_changed=
handle_pkg_as_is "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${s}" "${s}" "${old_version}" hpc_slot_changed
if [[ -z ${hpc_slot_changed} ]]; then
rm -rf "${hpc_update_dir}"
else
hpc_changed=x
fi
;;
"${GV_LT}")
handle_pkg_downgrade "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${s}" "${s}" "${old_version}" "${new_version}"
hpc_changed=x
;;
esac
done
# A "sys-devel/binutils update" case - one old slot and one
# new slot, but different from each other.
if [[ ${#hpc_only_old_slots_set[@]} -eq 1 ]] && [[ ${#hpc_only_new_slots_set[@]} -eq 1 ]]; then
get_first_from_set hpc_only_old_slots_set hpc_old_s
old_verminmax=${old_slot_verminmax_map_ref["${hpc_old_s}"]:-}
get_first_from_set hpc_only_new_slots_set hpc_new_s
new_verminmax=${new_slot_verminmax_map_ref["${hpc_new_s}"]:-}
pkg_debug "jumping from slot ${hpc_old_s} (vmm: ${old_verminmax}) to slot ${hpc_new_s} (vmm: ${new_verminmax})"
if [[ -z "${old_verminmax}" ]] || [[ -z "${new_verminmax}" ]]; then
devel_warn \
"- no verminmax info available for old and/or new:" \
" - old package: ${old_name}" \
" - slot: ${hpc_old_s}" \
" - minmax: ${old_verminmax}" \
" - new package: ${new_name}" \
" - slot: ${hpc_new_s}" \
" - minmax: ${new_verminmax}"
else
update_dir "${new_name}" "${hpc_old_s}" "${hpc_new_s}" hpc_update_dir
mkdir -p "${hpc_update_dir}"
old_version=${old_verminmax%%:*}
new_version=${new_verminmax##*:}
gentoo_ver_cmp_out "${new_version}" "${old_version}" hpc_cmp_result
case ${hpc_cmp_result} in
"${GV_GT}")
handle_pkg_update "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${hpc_old_s}" "${hpc_new_s}" "${old_version}" "${new_version}"
hpc_changed=x
;;
"${GV_EQ}")
hpc_slot_changed=
handle_pkg_as_is "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${hpc_old_s}" "${hpc_new_s}" "${old_version}" hpc_slot_changed
if [[ -z ${hpc_slot_changed} ]]; then
rm -rf "${hpc_update_dir}"
else
hpc_changed=x
fi
;;
"${GV_LT}")
handle_pkg_downgrade "${pkg_to_tags_mvm_var_name}" "${old_name}" "${new_name}" "${hpc_old_s}" "${hpc_new_s}" "${old_version}" "${new_version}"
hpc_changed=x
;;
esac
fi
elif [[ ${#hpc_only_old_slots_set[@]} -gt 0 ]] || [[ ${#hpc_only_new_slots_set[@]} -gt 0 ]]; then
pkg_debug 'complicated slots situation, needs manual intervention'
lines=(
'- handle package update:'
' - old package name:'
" - name: ${old_name}"
' - slots:'
)
for s in "${!hpc_old_filtered_slots_set[@]}"; do
old_verminmax=${old_slot_verminmax_map_ref["${s}"]:-}
lines+=(" - ${s}, minmax: ${old_verminmax}")
done
lines+=(
' - new package name:'
" - name: ${new_name}"
' - slots:'
)
for s in "${!hpc_new_filtered_slots_set[@]}"; do
new_verminmax=${new_slot_verminmax_map_ref["${s}"]:-}
lines+=(" - ${s}, minmax: ${new_verminmax}")
done
manual "${lines[@]}"
fi
unset -n new_slot_verminmax_map_ref old_slot_verminmax_map_ref
# if nothing changed, drop the entire update directory for the
# package, and possibly the parent directory if it became
# empty (parent directory being a category directory, like
# sys-apps)
if [[ -z ${hpc_changed} ]]; then
pkg_debug 'no changes, dropping reports'
rm -rf "${hpc_update_dir_non_slot}"
dirname_out "${hpc_update_dir_non_slot}" hpc_category_dir
if dir_is_empty "${hpc_category_dir}"; then
rmdir "${hpc_category_dir}"
fi
fi
pkg_debug_disable
done
mvm_unset hpc_new_pkg_slot_verminmax_map_mvm
mvm_unset hpc_old_pkg_slot_verminmax_map_mvm
mvm_unset hpc_pkg_slots_set_mvm
}
# Gets the first item from the passed set.
#
# Mostly intended to "unwrap" a single-element set.
#
# Params:
#
# 1 - name of the set variable
# 2 - name of the variable where the element will be stored
function get_first_from_set() {
local -n set_ref=${1}; shift
local -n return_ref=${1}; shift
local item
for item in "${!set_ref[@]}"; do
return_ref=${item}
return 0
done
return_ref=''
}
# Write information to reports directory about the package update
# (meaning specifically that the new version is greater than the old
# one).
#
# Params:
#
# 1 - name of the package tags set mvm variable
# 2 - old package name
# 3 - new package name
# 4 - old package slot
# 5 - new package slot
# 6 - old version
# 7 - new version
function handle_pkg_update() {
local pkg_to_tags_mvm_var_name old_pkg new_pkg old_s new_s old new
pkg_to_tags_mvm_var_name=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
old_s=${1}; shift
new_s=${1}; shift
old=${1}; shift
new=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local old_no_r new_no_r
old_no_r=${old%-r+([0-9])}
new_no_r=${new%-r+([0-9])}
local pkg_name
pkg_name=${new_pkg#*/}
local -a lines
lines=( "0:from ${old} to ${new}")
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( "0:renamed from ${old_pkg}" )
fi
generate_ebuild_diff "${OLD_PORTAGE_STABLE}" "${NEW_PORTAGE_STABLE}" "${old_pkg}" "${new_pkg}" "${old_s}" "${new_s}" "${old}" "${new}"
local hpu_update_dir hpu_update_dir_non_slot
update_dir_non_slot "${new_pkg}" hpu_update_dir_non_slot
update_dir "${new_pkg}" "${old_s}" "${new_s}" hpu_update_dir
local diff_report_name
gen_varname diff_report_name
diff_report_declare "${diff_report_name}"
generate_cache_diff_report "${diff_report_name}" "${WORKDIR}/pkg-reports/old/portage-stable-cache" "${WORKDIR}/pkg-reports/new/portage-stable-cache" "${old_pkg}" "${new_pkg}" "${old}" "${new}"
local -n diff_report_ref=${diff_report_name}
local -n diff_lines_ref=${diff_report_ref[${DR_LINES_IDX}]}
lines+=( "${diff_lines_ref[@]}" )
unset -n diff_lines_ref
unset -n diff_report_ref
diff_report_unset "${diff_report_name}"
if [[ -s "${hpu_update_dir}/ebuild.diff" ]]; then
lines+=( '0:TODO: review ebuild.diff' )
fi
if [[ -s "${hpu_update_dir_non_slot}/other.diff" ]]; then
lines+=( '0:TODO: review other.diff' )
fi
lines+=( '0:TODO: review occurences' )
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( '0:TODO: review occurences-for-old-name' )
fi
local -a hpu_tags
tags_for_pkg "${pkg_to_tags_mvm_var_name}" "${new_pkg}" hpu_tags
if ver_test "${new_no_r}" -gt "${old_no_r}"; then
# version bump
generate_changelog_entry_stub "${pkg_name}" "${new_no_r}" "${hpu_tags[@]}"
lines+=( '0:release notes: TODO' )
fi
generate_summary_stub "${new_pkg}" "${hpu_tags[@]}" -- "${lines[@]}"
}
# Write information to reports directory about the modified package
# (meaning specifically that the new version is equal than the old
# one).
#
# Params:
#
# 1 - name of the package tags set mvm variable
# 2 - old package name
# 3 - new package name
# 4 - old package slot
# 5 - new package slot
# 6 - version
# 7 - name of a "bool" variable where info is stored if relevant files
# has changed (empty means nothing changed, non-empty means
# something has changed)
function handle_pkg_as_is() {
local pkg_to_tags_mvm_var_name old_pkg new_pkg old_s new_s v
pkg_to_tags_mvm_var_name=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
old_s=${1}; shift
new_s=${1}; shift
v=${1}; shift
local -n changed_ref=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local hpai_update_dir
update_dir "${new_pkg}" "${old_s}" "${new_s}" hpai_update_dir
local pkg_name
pkg_name=${new_pkg#/}
local -a lines
lines=( "0:still at ${v}" )
local renamed
renamed=
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( "0:renamed from ${old_pkg}" )
renamed=x
fi
generate_ebuild_diff "${OLD_PORTAGE_STABLE}" "${NEW_PORTAGE_STABLE}" "${old_pkg}" "${new_pkg}" "${old_s}" "${new_s}" "${v}" "${v}"
local hpai_update_dir_non_slot hpai_update_dir
update_dir_non_slot "${new_pkg}" hpai_update_dir_non_slot
update_dir "${new_pkg}" "${old_s}" "${new_s}" hpai_update_dir
local modified
modified=
local diff_report_name
gen_varname diff_report_name
diff_report_declare "${diff_report_name}"
generate_cache_diff_report "${diff_report_name}" "${WORKDIR}/pkg-reports/old/portage-stable-cache" "${WORKDIR}/pkg-reports/new/portage-stable-cache" "${old_pkg}" "${new_pkg}" "${v}" "${v}"
local -n diff_report_ref=${diff_report_name}
local -n diff_lines_ref=${diff_report_ref[${DR_LINES_IDX}]}
if [[ ${#diff_lines_ref[@]} -gt 0 ]]; then
lines+=( "${diff_lines_ref[@]}" )
modified=x
fi
unset -n diff_lines_ref
unset -n diff_report_ref
diff_report_unset "${diff_report_name}"
if [[ -s "${hpai_update_dir}/ebuild.diff" ]]; then
lines+=( '0:TODO: review ebuild.diff' )
modified=x
fi
if [[ -s "${hpai_update_dir_non_slot}/other.diff" ]]; then
lines+=( '0:TODO: review other.diff' )
modified=x
fi
if [[ -z ${renamed} ]] && [[ -z ${modified} ]]; then
# Nothing relevant has changed, return early.
return 0
fi
changed_ref=x
lines+=( '0:TODO: review occurences' )
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( '0:TODO: review occurences-for-old-name' )
fi
local -a hpai_tags
tags_for_pkg "${pkg_to_tags_mvm_var_name}" "${new_pkg}" hpai_tags
generate_summary_stub "${new_pkg}" "${hpai_tags[@]}" -- "${lines[@]}"
}
# Write information to reports directory about the package downgrade
# (meaning specifically that the new version is lower than the old
# one).
#
# Params:
#
# 1 - name of the package tags set mvm variable
# 2 - old package name
# 3 - new package name
# 4 - old package slot
# 5 - new package slot
# 6 - old version
# 7 - new version
function handle_pkg_downgrade() {
local pkg_to_tags_mvm_var_name old_pkg new_pkg old_s new_s old new
pkg_to_tags_mvm_var_name=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
old_s=${1}; shift
new_s=${1}; shift
old=${1}; shift
new=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local old_no_r new_no_r
old_no_r=${old%-r+([0-9])}
new_no_r=${new%-r+([0-9])}
local pkg_name
pkg_name=${new_pkg#*/}
local -a lines
lines=( "0:downgraded from ${old} to ${new}" )
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( "0:renamed from ${old_pkg}" )
fi
generate_ebuild_diff "${OLD_PORTAGE_STABLE}" "${NEW_PORTAGE_STABLE}" "${old_pkg}" "${new_pkg}" "${old_s}" "${new_s}" "${old}" "${new}"
local hpd_update_dir hpd_update_dir_non_slot
update_dir_non_slot "${new_pkg}" hpd_update_dir_non_slot
update_dir "${new_pkg}" "${old_s}" "${new_s}" hpd_update_dir
local diff_report_name
gen_varname diff_report_name
diff_report_declare "${diff_report_name}"
generate_cache_diff_report "${diff_report_name}" "${WORKDIR}/pkg-reports/old/portage-stable-cache" "${WORKDIR}/pkg-reports/new/portage-stable-cache" "${old_pkg}" "${new_pkg}" "${old}" "${new}"
local -n diff_report_ref=${diff_report_name}
local -n diff_lines_ref=${diff_report_ref[${DR_LINES_IDX}]}
lines+=( "${diff_lines_ref[@]}" )
unset -n diff_lines_ref
unset -n diff_report_ref
diff_report_unset "${diff_report_name}"
if [[ -s "${hpd_update_dir}/ebuild.diff" ]]; then
lines+=( '0:TODO: review ebuild.diff' )
fi
if [[ -s "${hpd_update_dir_non_slot}/other.diff" ]]; then
lines+=( '0:TODO: review other.diff' )
fi
lines+=( '0:TODO: review occurences' )
if [[ ${old_pkg} != "${new_pkg}" ]]; then
lines+=( '0:TODO: review occurences-for-old-name' )
fi
local -a hpd_tags
tags_for_pkg "${pkg_to_tags_mvm_var_name}" "${new_pkg}" hpd_tags
if ver_test "${new_no_r}" -lt "${old_no_r}"; then
# version bump
generate_changelog_entry_stub "${pkg_name}" "${new_no_r}" "${hpd_tags[@]}"
lines+=( "0:release notes: TODO" )
fi
generate_summary_stub "${new_pkg}" "${hpd_tags[@]}" -- "${lines[@]}"
}
# Retrieves tags for a package.
#
# Params:
#
# 1 - name of the package tags set mvm variable
# 2 - package name
# 3 - name of the array variable, where the tags will be stored
function tags_for_pkg() {
local pkg_to_tags_mvm_var_name pkg
pkg_to_tags_mvm_var_name=${1}; shift
pkg=${1}; shift
local -n tags_ref=${1}; shift
local tfp_tags_var_name
mvm_get "${pkg_to_tags_mvm_var_name}" "${pkg}" tfp_tags_var_name
pkg_debug_enable "${pkg}"
pkg_debug "checking for tags in ${pkg_to_tags_mvm_var_name}"
if [[ -z ${tfp_tags_var_name} ]]; then
pkg_debug "no tags available"
tags_ref=()
else
local -n tags_in_mvm_ref=${tfp_tags_var_name}
tags_ref=( "${tags_in_mvm_ref[@]}" )
pkg_debug "tags available: ${tags_in_mvm_ref[*]}"
fi
pkg_debug_disable
}
# Adds a changelog stub to changelog file in reports directory.
#
# Params:
# 1 - package name (shortened, without the category)
# 2 - version
# @ - package tags
function generate_changelog_entry_stub() {
local pkg_name v
pkg_name=${1}; shift
v=${1}; shift
# rest are tags
local -a applied_tags=()
for tag; do
case ${tag} in
PROD)
applied_tags+=( 'base' )
;;
*)
# add lower-cased tag
applied_tags+=( "${tag,,}" )
;;
esac
done
local gces_tags=''
if [[ ${#applied_tags[@]} -gt 0 ]]; then
join_by gces_tags ', ' "${applied_tags[@]}"
else
# no tags, it means it's an SDK package
gces_tags='SDK'
fi
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
printf '%s %s: %s ([%s](TODO))\n' '-' "${gces_tags}" "${pkg_name}" "${v}" >>"${REPORTS_DIR}/updates/changelog_stubs"
}
# Adds a stub to the summary file in reports directory.
#
# Params:
# 1 - package
# @ - tags followed by double dash followed by lines to append to the
# file
function generate_summary_stub() {
local pkg
pkg=${1}; shift
# rest are tags separated followed by double dash followed by lines
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a tags
tags=()
while [[ ${#} -gt 0 ]]; do
if [[ ${1} = '--' ]]; then
shift
break
fi
tags+=( "${1}" )
shift
done
# rest are lines
{
printf '%s %s:' '-' "${pkg}"
if [[ ${#tags[@]} -gt 0 ]]; then
printf ' [%s]' "${tags[@]}"
fi
printf '\n'
local indent_line indent line
for indent_line; do
indent=${indent_line%%:*}
line=${indent_line#*:}
if [[ ${indent} -gt 0 ]]; then
printf -- ' %.0s' $(seq 1 "${indent}")
fi
printf ' - %s\n' "${line}"
done
printf '\n'
} >>"${REPORTS_DIR}/updates/summary_stubs"
}
# Generate diffs between directories in old state and new state for a
# package.
#
# Params:
#
# 1 - path to portage-stable in old state
# 2 - path to portage-stable in new state
# 3 - old package name
# 4 - new package name
function generate_full_diffs() {
local old_ps new_ps old_pkg new_pkg
old_ps=${1}; shift
new_ps=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
local old_path new_path
old_path="${old_ps}/${old_pkg}"
new_path="${new_ps}/${new_pkg}"
local gfd_update_dir
update_dir_non_slot "${new_pkg}" gfd_update_dir
local -a common_diff_opts=(
--recursive
--unified=3
)
xdiff "${common_diff_opts[@]}" --new-file "${old_path}" "${new_path}" >"${gfd_update_dir}/full.diff"
xdiff "${common_diff_opts[@]}" --brief "${old_path}" "${new_path}" >"${gfd_update_dir}/brief-summary"
}
# Generate a diff between non-ebuild, non-Manifest files for old and
# new package.
#
# Params:
#
# 1 - path to portage-stable in old state
# 2 - path to portage-stable in new state
# 3 - old package name
# 4 - new package name
function generate_non_ebuild_diffs() {
local old_ps new_ps old_pkg new_pkg
old_ps=${1}; shift
new_ps=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
local old_path new_path
old_path="${old_ps}/${old_pkg}"
new_path="${new_ps}/${new_pkg}"
local gned_update_dir
update_dir_non_slot "${new_pkg}" gned_update_dir
local -a diff_opts=(
--recursive
--unified=3
# Show contents of deleted or added files too.
--new-file
# Ignore ebuilds and the Manifest file.
--exclude='*.ebuild'
--exclude='Manifest'
)
xdiff "${diff_opts[@]}" "${old_path}" "${new_path}" >"${gned_update_dir}/other.diff"
}
# Generate a diff between specific ebuilds for old and new package.
#
# Params:
#
# 1 - path to portage-stable in old state
# 2 - path to portage-stable in new state
# 3 - old package name
# 4 - new package name
# 5 - old package slot
# 6 - new package slot
# 7 - old package version
# 8 - new package version
function generate_ebuild_diff() {
local old_ps new_ps old_pkg new_pkg old_s new_s old new
old_ps=${1}; shift
new_ps=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
old_s=${1}; shift
new_s=${1}; shift
old=${1}; shift
new=${1}; shift
local old_pkg_name new_pkg_name
old_pkg_name=${old_pkg#*/}
new_pkg_name=${new_pkg#*/}
local old_path new_path
old_path="${old_ps}/${old_pkg}/${old_pkg_name}-${old}.ebuild"
new_path="${new_ps}/${new_pkg}/${new_pkg_name}-${new}.ebuild"
local ged_update_dir
update_dir "${new_pkg}" "${old_s}" "${new_s}" ged_update_dir
xdiff --unified=3 "${old_path}" "${new_path}" >"${ged_update_dir}/ebuild.diff"
}
function generate_cache_diff_report() {
local diff_report_var_name=${1}; shift
local old_cache_dir=${1}; shift
local new_cache_dir=${1}; shift
local old_pkg=${1}; shift
local new_pkg=${1}; shift
local old=${1}; shift
local new=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local old_entry=${old_cache_dir}/${old_pkg}-${old}
local new_entry=${new_cache_dir}/${new_pkg}-${new}
local old_cache_name new_cache_name
gen_varname old_cache_name
gen_varname new_cache_name
cache_file_declare "${old_cache_name}" "${new_cache_name}"
parse_cache_file "${old_cache_name}" "${old_entry}" "${ARCHES[@]}"
parse_cache_file "${new_cache_name}" "${new_entry}" "${ARCHES[@]}"
diff_cache_data "${old_cache_name}" "${new_cache_name}" "${diff_report_var_name}"
cache_file_unset "${old_cache_name}" "${new_cache_name}"
}
# Generate a report with information where the old and new packages
# are mentioned in entire scripts repository. May result in two
# separate reports if the package got renamed.
#
# 1 - path to scripts repo
# 2 - old package name
# 3 - new package name
function generate_package_mention_reports() {
local scripts old_pkg new_pkg
scripts=${1}; shift
old_pkg=${1}; shift
new_pkg=${1}; shift
local gpr_update_dir
update_dir_non_slot "${new_pkg}" gpr_update_dir
generate_mention_report_for_package "${scripts}" "${new_pkg}" >"${gpr_update_dir}/occurences"
if [[ ${old_pkg} != "${new_pkg}" ]]; then
generate_mention_report_for_package "${scripts}" "${old_pkg}" >"${gpr_update_dir}/occurences-for-old-name"
fi
}
# Generate a report with information where the package is mentioned in
# entire scripts repository.
#
# 1 - path to scripts repo
# 3 - package name
function generate_mention_report_for_package() {
local scripts pkg
scripts=${1}; shift
pkg=${1}; shift
local ps co
ps='sdk_container/src/third_party/portage-stable'
co='sdk_container/src/third_party/coreos-overlay'
yell "${pkg} in overlay profiles"
grep_pkg "${scripts}" "${pkg}" "${co}/profiles"
yell "${pkg} in Gentoo profiles"
grep_pkg "${scripts}" "${pkg}" "${ps}/profiles"
# shellcheck disable=SC2164 # we use set -e, so the script will exit if it fails
pushd "${scripts}/${co}" >/dev/null
yell "${pkg} in env overrides"
cat_entries "coreos/config/env/${pkg}"@(|-+([0-9])*)
yell "${pkg} in user patches"
local dir
for dir in "coreos/user-patches/${pkg}"@(|-+([0-9])*); do
echo "BEGIN DIRECTORY: ${dir}"
cat_entries "${dir}"/*
echo "END DIRECTORY: ${dir}"
done
# shellcheck disable=SC2164 # we use set -e, so the script will exit if it fails
popd >/dev/null
yell "${pkg} in overlay (outside profiles)"
grep_pkg "${scripts}" "${pkg}" "${co}" ":(exclude)${co}/profiles"
yell "${pkg} in Gentoo (outside profiles)"
grep_pkg "${scripts}" "${pkg}" "${ps}" ":(exclude)${ps}/profiles"
yell "${pkg} in scripts (outside overlay and Gentoo)"
grep_pkg "${scripts}" "${pkg}" ":(exclude)${ps}" ":(exclude)${co}"
}
# Gets a toplevel update reports directory for a package. This is
# where occurences and non-ebuild diffs are stored.
#
# Params:
#
# 1 - package name
# 2 - name of a variable where the path will be stored
function update_dir_non_slot() {
local pkg
pkg=${1}; shift
local -n dir_ref=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
dir_ref="${REPORTS_DIR}/updates/${pkg}"
}
# Gets a slot specific update reports directory for a package. This is
# where ebuild diffs are stored.
#
# Params:
#
# 1 - package name
# 2 - old slot
# 3 - new slot
# 4 - name of a variable where the path will be stored
function update_dir() {
local pkg old_s new_s
pkg=${1}; shift
old_s=${1}; shift
new_s=${1}; shift
local -n dir_ref=${1}; shift
# slots may have slashes in them - replace them with "-slash-"
local slot_dir
if [[ ${old_s} = "${new_s}" ]]; then
slot_dir=${old_s//\//-slash-}
else
slot_dir="${old_s//\//-slash-}-to-${new_s//\//-slash-}"
fi
local ud_non_slot_dir
update_dir_non_slot "${pkg}" ud_non_slot_dir
dir_ref="${ud_non_slot_dir}/${slot_dir}"
}
# Greps for a package name in selected directories of the passed
# repo. It prints, so the invocation needs to be captured.
#
# Params:
#
# 1 - path to scripts repo
# 2 - package name
# @ - directories in the repo to limit the search for
function grep_pkg() {
local scripts pkg
scripts=${1}; shift
pkg=${1}; shift
# rest are directories
GIT_PAGER='' git -C "${scripts}" grep "${pkg}"'\(-[0-9$]\|[^a-zA-Z0-9_-]\|$\)' -- "${@}" || :
}
# Prints the passed files preceding and following with BEGIN ENTRY and
# END ENTRY markers.
#
# Params:
#
# @ - the files to print
function cat_entries() {
for entry; do
echo "BEGIN ENTRY: ${entry}"
cat "${entry}"
echo "END ENTRY: ${entry}"
done
}
# Reads the listings and renames, handles updates of both packages and
# non-packages (eclasses, licenses, profiles, etc.)
function handle_gentoo_sync() {
#mvm_debug_enable hgs_pkg_to_tags_mvm
mvm_declare hgs_pkg_to_tags_mvm
process_listings hgs_pkg_to_tags_mvm
local -A hgs_renames_old_to_new_map=()
process_profile_updates_directory hgs_renames_old_to_new_map
handle_package_changes hgs_renames_old_to_new_map hgs_pkg_to_tags_mvm
mvm_unset hgs_pkg_to_tags_mvm
#mvm_debug_disable hgs_pkg_to_tags_mvm
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local old_head new_head
old_head=$(git -C "${OLD_STATE}" rev-parse HEAD)
new_head=$(git -C "${NEW_STATE}" rev-parse HEAD)
local -A non_package_updates_set
non_package_updates_set=()
local path in_ps category
if [[ "${old_head}" != "${new_head}" ]]; then
while read -r path; do
if [[ ${path} != "${PORTAGE_STABLE_SUFFIX}/"* ]]; then
continue
fi
in_ps=${path#"${PORTAGE_STABLE_SUFFIX}/"}
category=${in_ps%%/*}
case "${category}" in
eclass)
if [[ ${in_ps} != 'eclass/'+([^/])'.eclass' ]]; then
fail "unexpected updated file inside eclass directory: '${path}'"
fi
non_package_updates_set["${in_ps}"]=x
;;
licenses|metadata|profiles|scripts)
non_package_updates_set["${category}"]=x
;;
virtual|*-*)
# Package update, already handled
:
;;
*)
fail "unexpected updated file '${path}'"
;;
esac
done < <(git -C "${NEW_STATE}" diff-tree --no-commit-id --name-only -r "${old_head}" "${new_head}")
fi
local entry
for entry in "${!non_package_updates_set[@]}"; do
case "${entry}" in
eclass/*)
handle_eclass "${entry}"
;;
licenses)
handle_licenses
;;
metadata)
info "not handling metadata updates, skipping"
;;
profiles)
handle_profiles
;;
scripts)
handle_scripts
;;
*)
fail "unknown non-package update for ${entry}"
;;
esac
done
sort_summary_stubs
sort_changelog_stubs
}
# Sorts entries in the summary file if it exists.
function sort_summary_stubs() {
if [[ -f "${REPORTS_DIR}/updates/summary_stubs" ]]; then
sort_like_summary_stubs "${REPORTS_DIR}/updates/summary_stubs"
fi
}
# Sorts entries in the summary file.
#
# Lines look like as follows:
#
# -BEGIN-
# - dev-lang/python: [DEV]
# - from 3.11.4 to 3.11.5
# - no changes in ebuild
# - release notes: TODO
#
# - app-emulation/qemu:
# - from 8.0.3 to 8.0.4
# - no changes in ebuild
# - release notes: TODO
#
# -END-
function sort_like_summary_stubs() {
local f
f=${1}; shift
mvm_declare groups_mvm
local -a lines entries
lines=()
entries=()
local -A dups
dups=()
local REPLY line entry sss_lines_name dup_count
while read -r; do
if [[ -z ${REPLY} ]]; then
if [[ ${#lines[@]} -gt 0 ]]; then
line=${lines[0]}
entry=${line#-+([[:space:]])}
entry=${entry%%:*}
dup_count=${dups["${entry}"]:-0}
if [[ ${dup_count} -gt 0 ]]; then
dup_count=$((dup_count + 1))
mvm_add groups_mvm "${entry}@${dup_count}" "${lines[@]}"
dups["${entry}"]=${dup_count}
else
mvm_get groups_mvm "${entry}" sss_lines_name
if [[ -n ${sss_lines_name} ]]; then
local -n lines_ref=${sss_lines_name}
mvm_add groups_mvm "${entry}@1" "${lines_ref[@]}"
unset -n lines_ref
mvm_remove groups_mvm "${entry}"
mvm_add groups_mvm "${entry}@2" "${lines[@]}"
dups["${entry}"]=2
else
mvm_add groups_mvm "${entry}" "${lines[@]}"
entries+=( "${entry}" )
fi
fi
lines=()
fi
else
lines+=( "${REPLY}" )
fi
done < <(cat "${f}"; echo) # echo for final empty line, just in case
if [[ ${#entries[@]} -eq 0 ]]; then
return 0
fi
local idx
{
while read -r line; do
dup_count=${dups["${line}"]:-0}
if [[ ${dup_count} -gt 0 ]]; then
idx=0
while [[ ${idx} -lt ${dup_count} ]]; do
idx=$((idx + 1))
mvm_get groups_mvm "${line}@${idx}" sss_lines_name
local -n lines_ref=${sss_lines_name}
printf '%s\n' "${lines_ref[@]}" ''
unset -n lines_ref
done
else
mvm_get groups_mvm "${line}" sss_lines_name
local -n lines_ref=${sss_lines_name}
printf '%s\n' "${lines_ref[@]}" ''
unset -n lines_ref
fi
done < <(printf '%s\n' "${entries[@]}" | csort)
} >"${f}"
mvm_unset groups_mvm
}
# Sorts entries in changelog stub if it exists.
function sort_changelog_stubs() {
if [[ -f "${REPORTS_DIR}/updates/changelog_stubs" ]]; then
sort_like_changelog_stubs "${REPORTS_DIR}/updates/changelog_stubs"
fi
}
# Sorts entries in changelog stub.
function sort_like_changelog_stubs() {
local f t
f=${1}; shift
t="${f}.tmp"
csort --output="${t}" "${f}"
mv -f "${t}" "${f}"
}
# Invokes sort with C locale. Meant to be used in bash pipelines.
#
# Params:
#
# @ - additional parameters to passed to sort
function csort() {
LC_ALL=C sort "${@}"
}
# Handle an eclass update. Basically generate a diff.
#
# Params:
#
# 1 - path to eclass file within an ebuild repo
function handle_eclass() {
local eclass
eclass=${1}; shift
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a lines
lines=()
if [[ -e "${OLD_PORTAGE_STABLE}/${eclass}" ]] && [[ -e "${NEW_PORTAGE_STABLE}/${eclass}" ]]; then
mkdir -p "${REPORTS_DIR}/updates/${eclass}"
xdiff --unified=3 "${OLD_PORTAGE_STABLE}/${eclass}" "${NEW_PORTAGE_STABLE}/${eclass}" >"${REPORTS_DIR}/updates/${eclass}/eclass.diff"
lines+=( '0:TODO: review the diff' )
elif [[ -e "${OLD_PORTAGE_STABLE}/${eclass}" ]]; then
lines+=( '0:unused, dropped' )
else
lines+=( '0:added from Gentoo' )
fi
generate_summary_stub "${eclass}" -- "${lines[@]}"
}
# Handle profile changes. Generates three different diffs - changes in
# relevant profiles (ancestors of the profiles used by board packages
# and SDK), a full diff between all the profiles, and a list of
# possibly irrelevant files that has changed too.
function handle_profiles() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a files=()
local which arch
for which in "${WHICH[@]}"; do
files+=("${WORKDIR}/pkg-reports/${which}/sdk-profiles")
for arch in "${ARCHES[@]}"; do
files+=("${WORKDIR}/pkg-reports/${which}/${arch}-board-profiles")
done
done
local -A profile_dirs_set
profile_dirs_set=()
local line
while read -r line; do
profile_dirs_set["${line}"]=x
done < <(xgrep --no-filename '^portage-stable:' "${files[@]}" | cut -d: -f2-)
local -a diff_opts
diff_opts=(
--recursive
--unified=3
--new-file # treat absent files as empty
)
local out_dir
out_dir="${REPORTS_DIR}/updates/profiles"
mkdir -p "${out_dir}"
xdiff "${diff_opts[@]}" \
"${OLD_PORTAGE_STABLE}/profiles" "${NEW_PORTAGE_STABLE}/profiles" >"${out_dir}/full.diff"
local relevant
relevant=''
local -a relevant_lines possibly_irrelevant_files
relevant_lines=()
possibly_irrelevant_files=()
local REPLY path dir mark
while read -r; do
if [[ ${REPLY} = "diff "* ]]; then
path=${REPLY##*"${NEW_PORTAGE_STABLE}/profiles/"}
dirname_out "${path}" dir
relevant=''
mark=${profile_dirs_set["${dir}"]:-}
if [[ -n "${mark}" ]]; then
relevant=x
else
case ${dir} in
.|desc|desc/*|updates|updates/*)
relevant=x
;;
esac
fi
if [[ -z ${relevant} ]]; then
possibly_irrelevant_files+=( "profiles/${path}" )
fi
fi
if [[ -n ${relevant} ]]; then
relevant_lines+=( "${REPLY}" )
fi
done <"${out_dir}/full.diff"
lines_to_file_truncate "${out_dir}/relevant.diff" "${relevant_lines[@]}"
lines_to_file_truncate "${out_dir}/possibly-irrelevant-files" "${possibly_irrelevant_files[@]}"
generate_summary_stub profiles -- '0:TODO: review the diffs'
}
# Handles changes in license directory. Generates brief reports and
# diffs about dropped, added or modified licenses.
function handle_licenses() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local -a dropped=() added=() changed=()
local line hl_stripped
# Lines are:
#
# Only in <PORTAGE_STABLE_X>/licenses: BSL-1.1
#
# or
#
# Files <PORTAGE_STABLE_1>/licenses/BSL-1.1 and <PORTAGE_STABLE_2>/licenses/BSL-1.1 differ
while read -r line; do
if [[ ${line} = 'Only in '* ]]; then
strip_out "${line##*:}" hl_stripped
if [[ ${line} = *"${OLD_STATE}"* ]]; then
dropped+=( "${hl_stripped}" )
elif [[ ${line} = *"${NEW_STATE}"* ]]; then
added+=( "${hl_stripped}" )
else
devel_warn "- unhandled license change: ${line}"
fi
elif [[ ${line} = 'Files '*' differ' ]]; then
line=${line##"Files ${OLD_PORTAGE_STABLE}/licenses/"}
line=${line%% *}
strip_out "${line}" hl_stripped
changed+=( "${hl_stripped}" )
else
devel_warn \
'- unhandled diff --brief line:' \
" - ${line}"
fi
done < <(xdiff --brief --recursive "${OLD_PORTAGE_STABLE}/licenses" "${NEW_PORTAGE_STABLE}/licenses")
local out_dir
out_dir="${REPORTS_DIR}/updates/licenses"
mkdir -p "${out_dir}"
lines_to_file_truncate \
"${out_dir}/brief-summary" \
'- removed:' \
"${dropped[@]/#/ - }" \
'- added:' \
"${added[@]/#/ - }" \
'- modified:' \
"${changed[@]/#/ - }"
truncate --size=0 "${out_dir}/modified.diff"
local c
for c in "${changed[@]}"; do
xdiff --unified=3 "${OLD_PORTAGE_STABLE}/licenses/${c}" "${NEW_PORTAGE_STABLE}/licenses/${c}" >>"${out_dir}/modified.diff"
done
local -a lines
lines=()
local joined
if [[ ${#dropped[@]} -gt 0 ]]; then
join_by joined ', ' "${dropped[@]}"
lines+=( "0:dropped ${joined}" )
fi
if [[ ${#added[@]} -gt 0 ]]; then
join_by joined ', ' "${added[@]}"
lines+=( "0:added ${joined}" )
fi
if [[ ${#changed[@]} -gt 0 ]]; then
join_by joined ', ' "${changed[@]}"
lines+=( "0:updated ${joined}" )
fi
generate_summary_stub licenses -- "${lines[@]}"
}
# Generates reports about changes inside the scripts directory.
function handle_scripts() {
# shellcheck source=for-shellcheck/globals
source "${WORKDIR}/globals"
local out_dir
out_dir="${REPORTS_DIR}/updates/scripts"
mkdir -p "${out_dir}"
xdiff --unified=3 --recursive "${OLD_PORTAGE_STABLE}/scripts" "${NEW_PORTAGE_STABLE}/scripts" >"${out_dir}/scripts.diff"
generate_summary_stub scripts -- '0:TODO: review the diffs'
}
fi