mirror of
https://github.com/LibreELEC/LibreELEC.tv
synced 2025-09-24 19:46:01 +07:00
See: https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html " ... Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting. The command substitution $(cat file) can be replaced by the equivalent but faster $(< file). " Testing indicates var=$(< file) is twice as fast as var=$(cat file).
188 lines
6.1 KiB
Plaintext
188 lines
6.1 KiB
Plaintext
# SPDX-License-Identifier: GPL-2.0
|
|
# Copyright (C) 2019-present Team LibreELEC (https://libreelec.tv)
|
|
|
|
THREADCOUNT=${THREADCOUNT:-100%}
|
|
|
|
# This function is passed a list of package.mk paths to be processed.
|
|
# Each package.mk is sourced with relevant variables output in JSON format.
|
|
json_worker() {
|
|
local packages="$@"
|
|
local pkgpath hierarchy exited
|
|
|
|
exit() { exited=1; }
|
|
|
|
. config/options ""
|
|
|
|
for pkgpath in ${packages}; do
|
|
pkgpath="${pkgpath%%@*}"
|
|
|
|
exited=0
|
|
if ! source_package "${pkgpath}/package.mk" &>/dev/null; then
|
|
unset -f exit
|
|
die "$(print_color CLR_ERROR "FAILURE: sourcing package ${pkgpath}/package.mk")"
|
|
fi
|
|
|
|
[ ${exited} -eq 1 ] && continue
|
|
|
|
[[ ${pkgpath} =~ ^${ROOT}/${PACKAGES}/ ]] && hierarchy="global" || hierarchy="local"
|
|
|
|
cat <<EOF
|
|
{
|
|
"name": "${PKG_NAME}",
|
|
"hierarchy": "${hierarchy}",
|
|
"section": "${PKG_SECTION}",
|
|
"bootstrap": "${PKG_DEPENDS_BOOTSTRAP}",
|
|
"init": "${PKG_DEPENDS_INIT}",
|
|
"host": "${PKG_DEPENDS_HOST}",
|
|
"target": "${PKG_DEPENDS_TARGET}"
|
|
},
|
|
EOF
|
|
done
|
|
}
|
|
export -f json_worker
|
|
|
|
# This function is passed the build instruction for a single job.
|
|
# The function will run either "build <package>" or "install <package>".
|
|
# ${slot} is the job slot number, ie. 1-8 when THREADCOUNT=8.
|
|
# ${job} is the sequence within the total number of ${jobs}.
|
|
package_worker() {
|
|
local slot=$1 job=$2 jobs=$3 args="$4"
|
|
local task pkgname result status
|
|
local addon istarget isaddon
|
|
|
|
export MTJOBID=${slot} MTMAXJOBS=${jobs}
|
|
|
|
read -r task pkgname <<< "${args}"
|
|
|
|
. config/options "${pkgname}"
|
|
|
|
[ ! -f "${THREAD_CONTROL}/parallel.pid" ] && echo "${PARALLEL_PID}" >"${THREAD_CONTROL}/parallel.pid"
|
|
|
|
${SCRIPTS}/${task} ${pkgname} 2>&1 && result=0 || result=1
|
|
|
|
[[ ${pkgname} =~ :target$ || "${pkgname//:/}" = "${pkgname}" ]] && istarget="yes" || istarget="no"
|
|
|
|
[[ "${MTADDONBUILD}" = "yes" && ( "${PKG_IS_ADDON}" = "yes" || "${PKG_IS_ADDON}" = "embedded" ) ]] && isaddon="yes" || isaddon="no"
|
|
|
|
if [ "${isaddon}" = "yes" -a "${istarget}" = "yes" ]; then
|
|
if [ ${result} -eq 0 ]; then
|
|
(
|
|
pkg_lock "${pkgname}" "packadd"
|
|
pkg_lock_status "ACTIVE" "${pkgname}" "packadd"
|
|
|
|
# cleanup old install path
|
|
rm -rf "${ADDON_BUILD}"
|
|
|
|
# install addon parts
|
|
if pkg_call_exists addon; then
|
|
pkg_call addon
|
|
else
|
|
install_binary_addon "${PKG_ADDON_ID}"
|
|
fi
|
|
|
|
# HACK for packages that provide multiple addons like screensavers.rsxs
|
|
# addon's addon() in package.mk should take care for exporting
|
|
# MULTI_ADDONS="addon.boo1 addon.boo2 addon.boo3"
|
|
if [ -n "${MULTI_ADDONS}" ] ; then
|
|
for addon in ${MULTI_ADDONS}; do
|
|
${SCRIPTS}/install_addon "${PKG_NAME}" "${addon}"
|
|
done
|
|
else
|
|
${SCRIPTS}/install_addon "${PKG_NAME}" "${PKG_ADDON_ID}"
|
|
fi
|
|
|
|
pkg_lock_status "UNLOCK" "${pkgname}" "packadd" "packed"
|
|
) 2>&1 || result=1
|
|
fi
|
|
|
|
if [ ${result} -ne 0 ]; then
|
|
if [ -d "${THREAD_CONTROL}/logs" ]; then
|
|
echo "${PKG_NAME} ${THREAD_CONTROL}/logs/${job}/stdout" >>"${THREAD_CONTROL}/addons.failed"
|
|
else
|
|
echo "${PKG_NAME}" >>"${THREAD_CONTROL}/addons.failed"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
(
|
|
flock --exclusive 95
|
|
[ ${result} -eq 0 ] && status="DONE" || status="FAIL"
|
|
num=$(< "${THREAD_CONTROL}/progress")
|
|
mv "${THREAD_CONTROL}/progress" "${THREAD_CONTROL}/progress.prev"
|
|
num=$((num + 1))
|
|
echo ${num} >"${THREAD_CONTROL}/progress"
|
|
printf "[%0*d/%0*d] [%-4s] %-7s %s\n" ${#jobs} ${num} ${#jobs} ${jobs} "${status}" "${task}" "${pkgname}" >&2
|
|
) 95>"${THREAD_CONTROL}/locks/.progress"
|
|
|
|
if [ ${result} -eq 0 ]; then
|
|
pkg_lock_status "IDLE"
|
|
else
|
|
pkg_lock_status "FAILED" "${pkgname}" "${task}"
|
|
|
|
print_color CLR_ERROR "FAILURE: $SCRIPTS/${task} ${pkgname} has failed!\n"
|
|
|
|
if [ -d "${THREAD_CONTROL}/logs" ]; then
|
|
cat >&2 <<EOF
|
|
|
|
The following logs for this failure are available:
|
|
stdout: ${THREAD_CONTROL}/logs/${job}/stdout
|
|
stderr: ${THREAD_CONTROL}/logs/${job}/stderr
|
|
|
|
EOF
|
|
fi
|
|
fi
|
|
|
|
return ${result}
|
|
}
|
|
export -f package_worker
|
|
|
|
start_multithread_build() {
|
|
local singlethread buildopts result=0
|
|
|
|
# init thread control folder
|
|
rm -rf "${THREAD_CONTROL}"
|
|
mkdir -p "${THREAD_CONTROL}/locks"
|
|
echo -1 >"${THREAD_CONTROL}/progress.prev"
|
|
echo 0 >"${THREAD_CONTROL}/progress"
|
|
touch "${THREAD_CONTROL}/status"
|
|
|
|
[ "${THREADCOUNT}" = "0" ] && THREADCOUNT=1
|
|
|
|
# Bootstrap GNU parallel
|
|
$SCRIPTS/build parallel:host 2>&1 || die "Unable to bootstrap parallel package"
|
|
|
|
# if number of detected slots is 1 then don't bother using inter-process locks as this is a sequential build
|
|
[ $(seq 1 32 | ${TOOLCHAIN}/bin/parallel --plain --no-notice --max-procs ${THREADCOUNT} echo {%} | sort -n | tail -1) -eq 1 ] && singlethread=yes || singlethread=no
|
|
|
|
# create a single log file by default if not using locks (single build process), or the builder is a masochist
|
|
if [ "${singlethread}" = "yes" -a "${ONELOG,,}" != "no" ] || [ "${ONELOG,,}" = "yes" ]; then
|
|
buildopts+=" --ungroup"
|
|
else
|
|
mkdir -p "${THREAD_CONTROL}/logs"
|
|
buildopts+=" --group --results ${THREAD_CONTROL}/logs/{#}/"
|
|
fi
|
|
|
|
# When building addons, don't halt on error - keep building all packages/addons
|
|
[ "${MTADDONBUILD}" = "yes" ] && buildopts+=" --halt never" || buildopts+=" --halt now,fail=1"
|
|
|
|
# pipefail: return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status
|
|
set -o pipefail
|
|
|
|
cat ${_CACHE_PACKAGE_GLOBAL} ${_CACHE_PACKAGE_LOCAL} | \
|
|
${TOOLCHAIN}/bin/parallel --plain --no-notice --max-args 30 --halt now,fail=1 json_worker | \
|
|
${SCRIPTS}/genbuildplan.py --no-reorder --show-wants --build ${@} > "${THREAD_CONTROL}"/plan || result=1
|
|
|
|
if [ ${result} -eq 0 ]; then
|
|
cat "${THREAD_CONTROL}"/plan | awk '{print $1 " " $2}' | \
|
|
MTBUILDSTART=$(date +%s) MTWITHLOCKS=yes ${TOOLCHAIN}/bin/parallel \
|
|
--plain --no-notice --max-procs ${THREADCOUNT} --joblog="${THREAD_CONTROL}/joblog" --plus ${buildopts} \
|
|
package_worker {%} {#} {##} {} || result=1
|
|
|
|
rm -f "${THREAD_CONTROL}/parallel.pid"
|
|
fi
|
|
|
|
set +o pipefail
|
|
|
|
return ${result}
|
|
}
|