mirror of
https://github.com/RetroPie/RetroPie-Setup.git
synced 2025-04-02 10:51:41 -04:00
1075 lines
37 KiB
Bash
Executable file
1075 lines
37 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
# This file is part of The RetroPie Project
|
|
#
|
|
# The RetroPie Project is the legal property of its developers, whose names are
|
|
# too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source.
|
|
#
|
|
# See the LICENSE.md file at the top-level directory of this distribution and
|
|
# at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md
|
|
#
|
|
|
|
declare -A __sections=(
|
|
[core]="core"
|
|
[main]="main"
|
|
[opt]="optional"
|
|
[exp]="experimental"
|
|
[driver]="driver"
|
|
[config]="configuration"
|
|
[depends]="dependency"
|
|
)
|
|
|
|
__NET_ERRMSG=""
|
|
|
|
function rp_listFunctions() {
|
|
local id
|
|
local desc
|
|
local mode
|
|
local func
|
|
local enabled
|
|
|
|
echo -e "ID: Description: List of available functions"
|
|
echo "-----------------------------------------------------------------------------------------------------------------------------------"
|
|
for id in ${__mod_id[@]}; do
|
|
if rp_isEnabled "$id"; then
|
|
printf "%-20s: %-42s :" "$id" "${__mod_info[$id/desc]}"
|
|
else
|
|
printf "*%-20s: %-42s : %s\n" "$id" "${__mod_info[$id/desc]}" "This module is not available for your platform"
|
|
continue
|
|
fi
|
|
while read mode; do
|
|
# skip private module functions (start with an underscore)
|
|
[[ "$mode" = _* ]] && continue
|
|
mode=${mode//_$id/}
|
|
echo -n " $mode"
|
|
done < <(compgen -A function -X \!*_$id)
|
|
fnExists "sources_${id}" && echo -n " clean"
|
|
fnExists "install_${id}" || fnExists "install_bin_${id}" && ! fnExists "remove_${id}" && echo -n " remove"
|
|
echo ""
|
|
done
|
|
echo "==================================================================================================================================="
|
|
}
|
|
|
|
function rp_printUsageinfo() {
|
|
echo -e "Usage:\n$0 <ID>\nThis will run the functions depends, sources, build, install, configure and clean automatically.\n"
|
|
echo -e "Alternatively, $0 can be called as\n$0 <ID> [function] where function is a supported module functions as listed below\n"
|
|
echo "Details of some common functions:"
|
|
echo "depends: install the dependencies for the module."
|
|
echo "sources: install the sources for the module"
|
|
echo "build: build/compile the module"
|
|
echo "install: install the compiled module"
|
|
echo "configure: configure the installed module (es_systems.cfg / launch parameters etc)"
|
|
echo "clean: remove the sources/build folder for the module"
|
|
echo "remove: remove/uninstall the module"
|
|
echo "help: get additional help on the module (available for all modules)"
|
|
echo -e "\nThis is a list of valid modules/packages and supported functions:\n"
|
|
rp_listFunctions
|
|
}
|
|
|
|
function rp_moduleVars() {
|
|
local id="$1"
|
|
|
|
# create variables that can be used in modules
|
|
local code
|
|
read -d "" -r code <<_EOF_
|
|
local md_desc="${__mod_info[$id/desc]}"
|
|
local md_help="${__mod_info[$id/help]}"
|
|
local md_type="${__mod_info[$id/type]}"
|
|
local md_flags="${__mod_info[$id/flags]}"
|
|
local md_path="${__mod_info[$id/path]}"
|
|
|
|
local md_licence="${__mod_info[$id/licence]}"
|
|
|
|
local md_repo_type="${__mod_info[$id/repo_type]}"
|
|
local md_repo_url="${__mod_info[$id/repo_url]}"
|
|
local md_repo_branch="${__mod_info[$id/repo_branch]}"
|
|
local md_repo_commit="${__mod_info[$id/repo_commit]}"
|
|
|
|
local md_build="$__builddir/$id"
|
|
local md_inst="$(rp_getInstallPath $id)"
|
|
# get module path folder + md_id for $md_data
|
|
local md_data="${__mod_info[$id/path]%/*}/$id"
|
|
_EOF_
|
|
|
|
echo "$code"
|
|
}
|
|
|
|
function rp_callModule() {
|
|
local md_id="$1"
|
|
local mode="$2"
|
|
# shift the function parameters left so $@ will contain any additional parameters which we can use in modules
|
|
shift 2
|
|
|
|
# check if module exists
|
|
if ! rp_hasModule "$md_id"; then
|
|
printMsgs "console" "No module '$md_id' found."
|
|
return 2
|
|
fi
|
|
|
|
# check if module is enabled for platform
|
|
if ! rp_isEnabled "$md_id"; then
|
|
printMsgs "console" "Module '$md_id' is not available for your system ($__platform)"
|
|
return 3
|
|
fi
|
|
|
|
# skip for modules 'builder' and 'setup' so that distcc settings do not propagate from them
|
|
if [[ "$md_id" != "builder" && "$md_id" != "setup" ]]; then
|
|
# if distcc is used and the module doesn't exclude it, add /usr/lib/distcc to PATH and MAKEFLAGS
|
|
if [[ -n "$DISTCC_HOSTS" ]] && ! hasFlag "${__mod_info[$md_id/flags]}" "nodistcc"; then
|
|
# use local variables so they are available to all child functions without changing the globals
|
|
local PATH="/usr/lib/distcc:$PATH"
|
|
local MAKEFLAGS="$MAKEFLAGS PATH=$PATH"
|
|
fi
|
|
fi
|
|
|
|
# parameters _auto_ _binary or _source_ (_source_ is used if no parameters are given for a module)
|
|
case "$mode" in
|
|
# install the module if not installed, and update if it is
|
|
_autoupdate_)
|
|
if rp_isInstalled "$md_id"; then
|
|
rp_callModule "$md_id" "_update_" || return 1
|
|
else
|
|
rp_callModule "$md_id" "_auto_" || return 1
|
|
fi
|
|
return 0
|
|
;;
|
|
# automatic modes used by rp_installModule to choose between binary/source based on pkg info
|
|
_auto_|_update_)
|
|
# if updating and a package isn't installed, return an error
|
|
if [[ "$mode" == "_update_" ]] && ! rp_isInstalled "$md_id"; then
|
|
__ERRMSGS+=("$md_id is not installed, so can't update")
|
|
return 1
|
|
fi
|
|
|
|
rp_loadPackageInfo "$md_id" "pkg_origin"
|
|
local pkg_origin="${__mod_info[$md_id/pkg_origin]}"
|
|
|
|
local has_binary=0
|
|
local has_net=0
|
|
|
|
isConnected && has_net=1
|
|
|
|
# for modules with nonet flag that don't need to download data, we force has_net to 1
|
|
hasFlag "${__mod_info[$md_id/flags]}" "nonet" && has_net=1
|
|
|
|
if [[ "$has_net" -eq 1 ]]; then
|
|
rp_hasBinary "$md_id"
|
|
local ret="$?"
|
|
[[ "$ret" -eq 0 ]] && has_binary=1
|
|
[[ "$ret" -eq 2 ]] && has_net=0
|
|
fi
|
|
|
|
# fail if we don't seem to be connected
|
|
if [[ "$has_net" -eq 0 ]]; then
|
|
__ERRMSGS+=("Can't install/update $md_id - $__NET_ERRMSG")
|
|
return 1
|
|
fi
|
|
|
|
local do_update=0
|
|
|
|
# if we are in _update_ mode we only update if there is a newer version of the binary or source
|
|
if [[ "$mode" == "_update_" ]]; then
|
|
printMsgs "heading" "Checking for updates for $md_id"
|
|
rp_hasNewerModule "$md_id" "$pkg_origin"
|
|
[[ "$?" -eq 0 || "$?" == 2 ]] && do_update=1
|
|
# if rp_hasNewerModule returns 3, then there was an error and we should abort
|
|
[[ "$?" -eq 3 ]] && return 1
|
|
else
|
|
do_update=1
|
|
fi
|
|
|
|
if [[ "$do_update" -eq 1 ]]; then
|
|
printMsgs "console" "Update is available - updating ..."
|
|
else
|
|
printMsgs "console" "No update was found."
|
|
fi
|
|
|
|
if [[ "$do_update" -eq 1 ]]; then
|
|
if [[ "$pkg_origin" != "source" && "$has_binary" -eq 1 ]]; then
|
|
rp_callModule "$md_id" _binary_ || return 1
|
|
else
|
|
rp_callModule "$md_id" _source_ || return 1
|
|
fi
|
|
fi
|
|
return 0
|
|
;;
|
|
_binary_)
|
|
for mode in depends install_bin configure; do
|
|
rp_callModule "$md_id" "$mode" || return 1
|
|
done
|
|
return 0
|
|
;;
|
|
# automatically build/install module from source if no _source_ or no parameters are given
|
|
""|_source_)
|
|
for mode in depends sources build install configure clean; do
|
|
rp_callModule "$md_id" "$mode" || return 1
|
|
done
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
# load our md_* variables
|
|
eval "$(rp_moduleVars $md_id)"
|
|
|
|
local md_mode="install"
|
|
|
|
# set md_conf_root to $configdir and to $configdir/ports for ports
|
|
# ports in libretrocores or systems (as ES sees them) in ports will need to change it manually with setConfigRoot
|
|
local md_conf_root
|
|
if [[ "$md_type" == "ports" ]]; then
|
|
setConfigRoot "ports"
|
|
else
|
|
setConfigRoot ""
|
|
fi
|
|
|
|
case "$mode" in
|
|
# remove sources
|
|
clean)
|
|
if [[ "$__persistent_repos" -eq 1 ]]; then
|
|
if [[ -d "$md_build/.git" ]]; then
|
|
git -C "$md_build" reset --hard
|
|
git -C "$md_build" clean -f -d
|
|
fi
|
|
else
|
|
rmDirExists "$md_build"
|
|
fi
|
|
return 0
|
|
;;
|
|
# create binary archive
|
|
create_bin)
|
|
rp_createBin || return 1
|
|
return 0
|
|
;;
|
|
# echo module help to console
|
|
help)
|
|
printMsgs "console" "$md_desc\n\n$md_help"
|
|
return 0;
|
|
;;
|
|
esac
|
|
|
|
# create function name
|
|
function="${mode}_${md_id}"
|
|
|
|
# automatically switch to install_bin if present while install is not, and handle cases where we have
|
|
# fallback functions when not present in modules - currently install_bin and remove
|
|
if ! fnExists "$function"; then
|
|
if [[ "$mode" == "install" ]] && fnExists "install_bin_${md_id}"; then
|
|
function="install_bin_${md_id}"
|
|
mode="install_bin"
|
|
elif [[ "$mode" != "install_bin" && "$mode" != "remove" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# these can be returned by a module
|
|
local md_ret_require=()
|
|
local md_ret_files=()
|
|
local md_ret_errors=()
|
|
local md_ret_info=()
|
|
|
|
local action
|
|
local pushed=1
|
|
case "$mode" in
|
|
depends)
|
|
if [[ "$1" == "remove" ]]; then
|
|
md_mode="remove"
|
|
action="Removing"
|
|
else
|
|
action="Installing"
|
|
fi
|
|
action+=" dependencies for"
|
|
;;
|
|
sources)
|
|
action="Getting sources for"
|
|
mkdir -p "$md_build"
|
|
pushd "$md_build"
|
|
pushed=$?
|
|
;;
|
|
build)
|
|
action="Building"
|
|
pushd "$md_build" 2>/dev/null
|
|
pushed=$?
|
|
;;
|
|
install)
|
|
pushd "$md_build" 2>/dev/null
|
|
pushed=$?
|
|
;;
|
|
install_bin)
|
|
action="Installing (binary)"
|
|
;;
|
|
configure)
|
|
action="Configuring"
|
|
pushd "$md_inst" 2>/dev/null
|
|
pushed=$?
|
|
;;
|
|
remove)
|
|
action="Removing"
|
|
;;
|
|
_update_hook)
|
|
;;
|
|
*)
|
|
action="Running action '$mode' for"
|
|
;;
|
|
esac
|
|
|
|
# print an action and a description
|
|
if [[ -n "$action" ]]; then
|
|
printHeading "$action '$md_id' : $md_desc"
|
|
fi
|
|
|
|
case "$mode" in
|
|
remove)
|
|
fnExists "$function" && "$function" "$@"
|
|
md_mode="remove"
|
|
if fnExists "configure_${md_id}"; then
|
|
pushd "$md_inst" 2>/dev/null
|
|
pushed=$?
|
|
"configure_${md_id}"
|
|
fi
|
|
rm -rf "$md_inst"
|
|
rp_clearCachedInfo "$md_id"
|
|
printMsgs "console" "Removed directory $md_inst"
|
|
;;
|
|
install)
|
|
action="Installing"
|
|
# remove any previous install folder unless noinstclean flag is set
|
|
if ! hasFlag "${__mod_info[$md_id/flags]}" "noinstclean"; then
|
|
rmDirExists "$md_inst"
|
|
fi
|
|
mkdir -p "$md_inst"
|
|
"$function" "$@"
|
|
;;
|
|
install_bin)
|
|
if fnExists "install_bin_${md_id}"; then
|
|
mkdir -p "$md_inst"
|
|
if ! "$function" "$@"; then
|
|
md_ret_errors+=("Unable to install binary for $md_id")
|
|
fi
|
|
else
|
|
if rp_hasBinary "$md_id"; then
|
|
rp_installBin
|
|
else
|
|
md_ret_errors+=("Could not find a binary for $md_id")
|
|
fi
|
|
fi
|
|
;;
|
|
*)
|
|
# call the function with parameters
|
|
"$function" "$@"
|
|
;;
|
|
esac
|
|
|
|
# check if any required files are found
|
|
if [[ -n "$md_ret_require" ]]; then
|
|
for file in "${md_ret_require[@]}"; do
|
|
if [[ ! -e "$file" ]]; then
|
|
md_ret_errors+=("Could not successfully $mode $md_id - $md_desc ($file not found).")
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ "${#md_ret_errors}" -eq 0 && -n "$md_ret_files" ]]; then
|
|
# check for existence and copy any files/directories returned
|
|
local file
|
|
for file in "${md_ret_files[@]}"; do
|
|
if [[ ! -e "$md_build/$file" ]]; then
|
|
md_ret_errors+=("Could not successfully install $md_desc ($md_build/$file not found).")
|
|
break
|
|
fi
|
|
cp -Rvf "$md_build/$file" "$md_inst"
|
|
done
|
|
fi
|
|
|
|
# remove build folder if empty
|
|
[[ -d "$md_build" ]] && find "$md_build" -maxdepth 0 -empty -exec rmdir {} \;
|
|
|
|
local ret=0
|
|
# some errors were returned.
|
|
if [[ "${#md_ret_errors[@]}" -gt 0 ]]; then
|
|
__ERRMSGS+=("${md_ret_errors[@]}")
|
|
printMsgs "console" "${md_ret_errors[@]}" >&2
|
|
# if sources fails and we were called from the setup gui module clean sources
|
|
if [[ "$mode" == "sources" && "$__setup" -eq 1 ]]; then
|
|
rp_callModule "$md_id" clean
|
|
fi
|
|
# remove install folder if there is an error (and it is empty)
|
|
[[ -d "$md_inst" ]] && find "$md_inst" -maxdepth 0 -empty -exec rmdir {} \;
|
|
ret=1
|
|
else
|
|
[[ "$mode" == "install_bin" ]] && rp_setPackageInfo "$md_id" "binary"
|
|
[[ "$mode" == "install" ]] && rp_setPackageInfo "$md_id" "source"
|
|
# handle the case of a few drivers that don't have an install function and set the package info at build stage
|
|
if ! fnExists "install_${md_id}" && [[ "$mode" == "build" ]]; then
|
|
rp_setPackageInfo "$md_id" "source"
|
|
fi
|
|
fi
|
|
|
|
# some information messages were returned
|
|
if [[ "${#md_ret_info[@]}" -gt 0 ]]; then
|
|
__INFMSGS+=("${md_ret_info[@]}")
|
|
fi
|
|
|
|
[[ "$pushed" -eq 0 ]] && popd
|
|
|
|
return "$ret"
|
|
}
|
|
|
|
function rp_hasBinaries() {
|
|
[[ "$__has_binaries" -eq 1 ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
function rp_getBinaryUrl() {
|
|
local id="$1"
|
|
local url=""
|
|
rp_hasBinaries && url="$__binary_url/${__mod_info[$id/type]}/$id.tar.gz"
|
|
if fnExists "install_bin_${id}"; then
|
|
if fnExists "__binary_url_${id}"; then
|
|
url="$(__binary_url_${id})"
|
|
else
|
|
url="notest"
|
|
fi
|
|
fi
|
|
echo "$url"
|
|
}
|
|
|
|
# returns 0 if file exists, 1 if it doesn't and 2 on other error
|
|
function rp_remoteFileExists() {
|
|
local url="$1"
|
|
local ret
|
|
# runCurl will cause stderr to be copied to output so we redirect both stdout/stderr to /dev/null.
|
|
# any errors will have been captured by runCurl
|
|
runCurl --location --max-time 10 --silent --show-error --fail --head "$url" &>/dev/null
|
|
ret="$?"
|
|
if [[ "$ret" -eq 0 ]]; then
|
|
return 0
|
|
elif [[ "$ret" -eq 22 ]]; then
|
|
return 1
|
|
else
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
function rp_hasBinary() {
|
|
local id="$1"
|
|
|
|
# binary blacklist for armv7 Debian/OSMC due to GCC ABI incompatibility with
|
|
# threaded C++ apps on Raspbian (armv6 userland)
|
|
if [[ "$__os_id" != "Raspbian" ]] && ! isPlatform "armv6"; then
|
|
case "$id" in
|
|
emulationstation|lzdoom|lr-dinothawr|lr-ppsspp|ppsspp)
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local url="$(rp_getBinaryUrl $id)"
|
|
[[ "$url" == "notest" ]] && return 0
|
|
[[ -z "$url" ]] && return 1
|
|
|
|
[[ -n "${__mod_info[$id/has_binary]}" ]] && return "${__mod_info[$id/has_binary]}"
|
|
|
|
local ret=1
|
|
rp_remoteFileExists "$url"
|
|
ret="$?"
|
|
|
|
# if there wasn't an error, cache the result
|
|
[[ "$ret" -ne 2 ]] && __mod_info[$id/has_binary]="$ret"
|
|
return "$ret"
|
|
}
|
|
|
|
function rp_getFileDate() {
|
|
local url="$1"
|
|
[[ -z "$url" ]] && return 1
|
|
|
|
# get last-modified date stripping any CR in the output
|
|
local file_date=$(runCurl --location --silent --fail --head --no-styled-output "$url" | tr -d "\r" | grep -ioP "last-modified: \K.+")
|
|
# if there is a date set in last-modified header, then convert to iso-8601 format
|
|
if [[ -n "$file_date" ]]; then
|
|
file_date="$(date -Iseconds --date="$file_date")"
|
|
echo "$file_date"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
function rp_getBinaryDate() {
|
|
local id="$1"
|
|
local url="$(rp_getBinaryUrl $id)"
|
|
[[ -z "$url" || "$url" == "notest" ]] && return 1
|
|
local bin_date
|
|
if bin_date="$(rp_getFileDate "$url")"; then
|
|
echo "$bin_date"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
function rp_dateIsNewer() {
|
|
local date_a="$1"
|
|
local date_b="$2"
|
|
[[ -z "$date_a" || -z "$date_b" ]] && return 0
|
|
if date_a="$(date -d "$date_a" +%s 2>/dev/null)" && date_b="$(date -d "$date_b" +%s 2>/dev/null)"; then
|
|
[[ "$date_b" -gt "$date_a" ]] && return 0
|
|
else
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# resolve repository parameters - any parameters with a : will get the data from the following function name
|
|
function rp_resolveRepoParam() {
|
|
local param="$1"
|
|
if [[ "$param" == :* ]]; then
|
|
param="${param:1}"
|
|
if fnExists "$param"; then
|
|
echo "$($param)"
|
|
return
|
|
fi
|
|
fi
|
|
echo "$param"
|
|
}
|
|
|
|
# gets remote repository hash/revision - echos hash of remote repo and returns 0, or
|
|
# echos an error and returns 1 on failure
|
|
function rp_getRemoteRepoHash() {
|
|
local type="$1"
|
|
local url="$2"
|
|
local branch="$3"
|
|
local commit="$4"
|
|
local hash
|
|
local ret
|
|
local cmd=()
|
|
set -o pipefail
|
|
case "$type" in
|
|
git)
|
|
# when the remote repository uses an annotated git tag, the real commit is found by looking for the
|
|
# "tag^{}" reference, since the tag ref will point to the tag object itself, instead of the tagged
|
|
# commit. See gitrevisions(7).
|
|
cmd=(git ls-remote "$url" "$branch" "$branch^{}")
|
|
# grep to make sure we only return refs/heads/BRANCH and refs/tags/BRANCH in case there are additional
|
|
# references to the branch/tag eg refs/heads/SOMETHINGELSE/master which can be the case.
|
|
# we grab the last match reported by grep, as tags that also have a tag^{} reference
|
|
# will be displayed after.
|
|
hash=$("${cmd[@]}" 2>/dev/null | grep -P "\trefs/(heads|tags)/$branch" | tail -n1 | cut -f1)
|
|
;;
|
|
svn)
|
|
cmd=(svn info -r"$commit" "$url")
|
|
hash=$("${cmd[@]}" 2>/dev/null | grep -oP "Last Changed Rev: \K.*")
|
|
;;
|
|
esac
|
|
ret="$?"
|
|
set +o pipefail
|
|
if [[ "$ret" -ne 0 ]]; then
|
|
echo "${cmd[*]} failed with return code $ret - please check your network connection"
|
|
return 1
|
|
fi
|
|
echo "$hash"
|
|
return 0
|
|
}
|
|
|
|
# returns 0 if a module has a newer version, 1 if it doesn't, 2 if unknown (always update), or 3 if there is an error
|
|
function rp_hasNewerModule() {
|
|
local id="$1"
|
|
local type="$2"
|
|
|
|
[[ -n "${__mod_info[$id/has_newer]}" ]] && return "${__mod_info[$id/has_newer]}"
|
|
|
|
rp_loadPackageInfo "$id"
|
|
local pkg_origin="${__mod_info[$id/pkg_origin]}"
|
|
local pkg_date="${__mod_info[$id/pkg_date]}"
|
|
local pkg_repo_date="${__mod_info[$id/pkg_repo_date]}"
|
|
local pkg_repo_commit="${__mod_info[$id/pkg_repo_commit]}"
|
|
|
|
local ret=1
|
|
case "$type" in
|
|
binary)
|
|
ret=""
|
|
if [[ -n "$pkg_date" ]]; then
|
|
local bin_date="$(rp_getBinaryDate $id)"
|
|
if [[ -n "$bin_date" ]]; then
|
|
rp_dateIsNewer "$pkg_date" "$bin_date"
|
|
ret="$?"
|
|
fi
|
|
fi
|
|
[[ -z "$ret" ]] && ret=2
|
|
;;
|
|
source)
|
|
local repo_type="${__mod_info[$id/repo_type]}"
|
|
local repo_url="$(rp_resolveRepoParam "${__mod_info[$id/repo_url]}")"
|
|
case "$repo_type" in
|
|
file)
|
|
if [[ -n "$pkg_repo_date" ]]; then
|
|
local file_date="$(rp_getFileDate "$repo_url")"
|
|
rp_dateIsNewer "$pkg_repo_date" "$file_date"
|
|
ret="$?"
|
|
fi
|
|
;;
|
|
git|svn)
|
|
local repo_branch="$(rp_resolveRepoParam "${__mod_info[$id/repo_branch]}")"
|
|
local repo_commit="$(rp_resolveRepoParam "${__mod_info[$id/repo_commit]}")"
|
|
# if we are locked to a single commit, then we compare against the current module commit only
|
|
if [[ -n "$repo_commit" && "$repo_commit" != "HEAD" ]]; then
|
|
# if we are using git and the module has a commit hash, then adjust
|
|
# the package commit length for the comparison
|
|
if [[ "$repo_type" == "git" && "${#repo_commit}" -ge 4 ]]; then
|
|
pkg_repo_commit="${pkg_repo_commit::${#repo_commit}}"
|
|
fi
|
|
if [[ "$pkg_repo_commit" != "$repo_commit" ]]; then
|
|
ret=0
|
|
fi
|
|
else
|
|
local remote_commit
|
|
if remote_commit="$(rp_getRemoteRepoHash "$repo_type" "$repo_url" "$repo_branch" "$repo_commit")"; then
|
|
if [[ -n "$remote_commit" && "$pkg_repo_commit" != "$remote_commit" ]]; then
|
|
ret=0
|
|
fi
|
|
else
|
|
__ERRMSGS+=("$remote_commit")
|
|
ret=3
|
|
fi
|
|
fi
|
|
;;
|
|
:*)
|
|
local pkg_repo_extra="${__mod_info[$id/pkg_repo_extra]}"
|
|
# handle checking via module function - function should return 0 if there is a newer version
|
|
local function="${repo_type:1}"
|
|
if fnExists "$function"; then
|
|
"$function" newer
|
|
ret="$?"
|
|
fi
|
|
;;
|
|
*)
|
|
# fallback on forcing an update
|
|
ret=2
|
|
;;
|
|
esac
|
|
|
|
# if we are currently not going to update - check the last commit date of the module code
|
|
# if it's newer than the install date of the module we force an update, in case patches or build
|
|
# related options have been changed
|
|
if [[ "$ret" -eq 1 && "$__ignore_module_date" -ne 1 ]]; then
|
|
local vendor="${__mod_info[$id/vendor]}"
|
|
local repo_dir="$scriptdir"
|
|
[[ "$vendor" != "RetroPie" ]] && repo_dir+="/ext/$vendor"
|
|
local module_date="$(sudo -u "$__user" git -C "$repo_dir" log -1 --format=%cI -- "${__mod_info[$id/path]}")"
|
|
# just in case the module is not known to git, get the file last modified date
|
|
[[ -z "$module_date" ]] && module_date="$(date -Iseconds -r "${__mod_info[$id/path]}")"
|
|
if rp_dateIsNewer "$pkg_date" "$module_date"; then
|
|
ret=0
|
|
fi
|
|
fi
|
|
;;
|
|
*)
|
|
# for unknown or in the case of a blank pkg_origin assume there is an update
|
|
ret=2
|
|
;;
|
|
esac
|
|
|
|
# cache our return value
|
|
__mod_info[$id/has_newer]="$ret"
|
|
|
|
return "$ret"
|
|
}
|
|
|
|
function rp_getInstallPath() {
|
|
local id="$1"
|
|
echo "$rootdir/${__mod_info[$id/type]}/$id"
|
|
}
|
|
|
|
function rp_installBin() {
|
|
rp_hasBinaries || fatalError "There are no binary archives for platform $__platform"
|
|
local archive="$md_id.tar.gz";
|
|
local dest="$rootdir/$md_type"
|
|
|
|
local cmd_out
|
|
|
|
# create temporary folder
|
|
local tmp=$(mktemp -d)
|
|
|
|
if downloadAndVerify "$__binary_url/$md_type/$archive" "$tmp/$archive"; then
|
|
mkdir -p "$dest"
|
|
rm -rf "$dest/$md_id"
|
|
if tar -xvf "$tmp/$archive" -C "$dest"; then
|
|
rm -rf "$tmp"
|
|
return 0
|
|
else
|
|
md_ret_errors+=("Archive $archive failed to unpack correctly to $dest")
|
|
fi
|
|
fi
|
|
|
|
rm -rf "$tmp"
|
|
return 1
|
|
}
|
|
|
|
function rp_createBin() {
|
|
printHeading "Creating binary archive for $md_desc"
|
|
|
|
if [[ ! -d "$md_inst" ]]; then
|
|
printMsgs "console" "No install directory $md_inst - no archive created"
|
|
return 1
|
|
fi
|
|
|
|
if dirIsEmpty "$md_inst"; then
|
|
printMsgs "console" "Empty install directory $md_inst - no archive created"
|
|
return 1
|
|
fi
|
|
|
|
local archive="$md_id.tar.gz"
|
|
local dest="$__tmpdir/archives/$__binary_path/$md_type"
|
|
mkdir -p "$dest"
|
|
rm -f "$dest/$archive" "$dest/$archive.asc"
|
|
local ret=1
|
|
if tar cvzf "$dest/$archive" -C "$rootdir/$md_type" "$md_id"; then
|
|
if [[ -n "$__gpg_signing_key" ]]; then
|
|
if signFile "$dest/$archive"; then
|
|
chown "$__user":"$__group" "$dest/$archive" "$dest/$archive.asc"
|
|
ret=0
|
|
fi
|
|
else
|
|
ret=0
|
|
fi
|
|
fi
|
|
if [[ "$ret" -eq 0 ]]; then
|
|
cp "$md_inst/retropie.pkg" "$dest/$md_id.pkg"
|
|
else
|
|
rm -f "$dest/$archive"
|
|
fi
|
|
return "$ret"
|
|
}
|
|
|
|
function rp_hasModule() {
|
|
local id="$1"
|
|
[[ -n "${__mod_idx[$id]}" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
function rp_installModule() {
|
|
local id="$1"
|
|
local mode="$2"
|
|
[[ -z "$mode" ]] && mode="_auto_"
|
|
rp_callModule "$id" "$mode" || return 1
|
|
return 0
|
|
}
|
|
|
|
function rp_clearCachedInfo() {
|
|
local id="$1"
|
|
# clear cached data
|
|
# set pkg_info to 0 to force a reload when needed
|
|
__mod_info[$id/pkg_info]=0
|
|
__mod_info[$id/has_binary]=""
|
|
__mod_info[$id/has_newer]=""
|
|
}
|
|
|
|
# this is run after the install_ID function for a module - which by default would be from the
|
|
# folder set by md_build. However some modules install directly to md_inst which would mean unless
|
|
# they change directory also to md_inst in the install stage (which is common), it's possible this
|
|
# function could be run and not find the source. Therefore, we currently record the first directory used
|
|
# by gitPullOrClone and record it in __mod_info to be used here - but this needs further work to handle
|
|
# other cases.
|
|
function rp_setPackageInfo() {
|
|
local id="$1"
|
|
local install_path="$(rp_getInstallPath $id)"
|
|
[[ ! -d "$install_path" ]] && return 1
|
|
local pkg="$install_path/retropie.pkg"
|
|
local origin="$2"
|
|
|
|
rp_clearCachedInfo "$id"
|
|
|
|
iniConfig "=" '"' "$pkg"
|
|
iniSet "pkg_origin" "$origin"
|
|
local pkg_date
|
|
local pkg_repo_type
|
|
local pkg_repo_url
|
|
local pkg_repo_branch
|
|
local pkg_repo_commit
|
|
local pkg_repo_date
|
|
local pkg_repo_extra
|
|
|
|
if [[ "$origin" == "binary" ]]; then
|
|
pkg_date="$(rp_getBinaryDate $id)"
|
|
iniSet "pkg_date" "$pkg_date"
|
|
else
|
|
pkg_date="$(date -Iseconds)"
|
|
pkg_repo_type="${__mod_info[$id/repo_type]}"
|
|
pkg_repo_url="$(rp_resolveRepoParam "${__mod_info[$id/repo_url]}")"
|
|
pkg_repo_branch="$(rp_resolveRepoParam "${__mod_info[$id/repo_branch]}")"
|
|
case "$pkg_repo_type" in
|
|
git|svn)
|
|
if [[ "$pkg_repo_type" == "git" ]]; then
|
|
# if we have recorded a source install dir during gitPullOrClone use it, or default to md_build
|
|
local repo_dir="${__mod_info[$id/repo_dir]}"
|
|
[[ -z "$repo_dir" ]] && repo_dir="$md_build"
|
|
# date cannot understand the default date format of git
|
|
pkg_repo_date="$(git -C "$repo_dir" log -1 --format=%cI)"
|
|
pkg_repo_commit="$(git -C "$repo_dir" log -1 --format=%H)"
|
|
else
|
|
pkg_repo_date="$(svn info . | grep -oP "Last Changed Date: \K.*")"
|
|
pkg_repo_date="$(date -Iseconds -d "$pkg_repo_date")"
|
|
pkg_repo_commit="$(svn info . | grep -oP "Last Changed Rev: \K.*")"
|
|
fi
|
|
;;
|
|
file)
|
|
pkg_repo_date="$(rp_getFileDate "$pkg_repo_url")"
|
|
;;
|
|
:*)
|
|
# set data based on function hook - function should return code to define any pkg_* vars
|
|
# eg. local pkg_repo_date="something"
|
|
local function="${pkg_repo_type:1}"
|
|
fnExists "$function" && eval $("$function" get)
|
|
;;
|
|
esac
|
|
|
|
iniSet "pkg_date" "$pkg_date"
|
|
iniSet "pkg_repo_type" "$pkg_repo_type"
|
|
iniSet "pkg_repo_url" "$pkg_repo_url"
|
|
iniSet "pkg_repo_branch" "$pkg_repo_branch"
|
|
iniSet "pkg_repo_commit" "$pkg_repo_commit"
|
|
iniSet "pkg_repo_date" "$pkg_repo_date"
|
|
iniSet "pkg_repo_extra" "$pkg_repo_extra"
|
|
fi
|
|
}
|
|
|
|
# loads installed package information into __mod_info/pkg_* fields
|
|
# additional parameters are optional keys to load, but the data won't be cached if this is provided
|
|
function rp_loadPackageInfo() {
|
|
local id="$1"
|
|
|
|
# if we have cached the package information already, return
|
|
[[ "${__mod_info[$id/pkg_info]}" -eq 1 ]] && return
|
|
|
|
local keys
|
|
local cache=1
|
|
if [[ -z "$2" ]]; then
|
|
keys=(
|
|
pkg_origin
|
|
pkg_date
|
|
pkg_repo_type
|
|
pkg_repo_url
|
|
pkg_repo_branch
|
|
pkg_repo_commit
|
|
pkg_repo_date
|
|
pkg_repo_extra
|
|
)
|
|
else
|
|
# get user supplied keys but don't cache
|
|
shift
|
|
keys=("$@")
|
|
cache=0
|
|
fi
|
|
|
|
local load=0
|
|
local pkg_file="$(rp_getInstallPath $id)/retropie.pkg"
|
|
|
|
local builder_pkg_file="$__tmpdir/archives/$__binary_path/${__mod_info[$id/type]}/$id.pkg"
|
|
# fallback to using package info for built binaries so the binary builder code can update only changed modules
|
|
if [[ ! -f "$pkg_file" && -f "$builder_pkg_file" ]]; then
|
|
pkg_file="$builder_pkg_file"
|
|
fi
|
|
|
|
# if the pkg file is available, we will load the data in the next loop
|
|
[[ -f "$pkg_file" ]] && load=1
|
|
|
|
local key
|
|
local data
|
|
for key in "${keys[@]}"; do
|
|
# clear any previous data we have stored, but default "pkg_origin" to "unknown"
|
|
data=""
|
|
[[ "$key" == "pkg_origin" ]] && data="unknown"
|
|
__mod_info[$id/$key]="$data"
|
|
|
|
# if the package file is available and the field is set, override the default values
|
|
if [[ "$load" -eq 1 ]]; then
|
|
data="$(grep -oP "$key=\"\K[^\"]+" "$pkg_file")"
|
|
[[ -n "$data" ]] && __mod_info[$id/$key]="$data"
|
|
fi
|
|
done
|
|
# if loading all data set pkg_info to 1 so we avoid loading when not needed
|
|
[[ "$cache" -eq 1 ]] && __mod_info[$id/pkg_info]=1
|
|
}
|
|
|
|
function rp_registerModule() {
|
|
local path="$1"
|
|
local type="$2"
|
|
local vendor="$3"
|
|
# type is the last folder in the path as we now send a full path to rp_registerModule
|
|
type="${type##*/}"
|
|
local rp_module_id=""
|
|
local rp_module_desc=""
|
|
local rp_module_help=""
|
|
local rp_module_licence=""
|
|
local rp_module_section=""
|
|
local rp_module_flags=""
|
|
local rp_module_repo=""
|
|
|
|
local error=0
|
|
|
|
# for 3rd party modules, extract the module id and make sure it is unique
|
|
# as we don't want 3rd party repos overriding our built-in modules
|
|
if [[ "$vendor" != "RetroPie" ]]; then
|
|
rp_module_id=$(grep -oP "rp_module_id=\"\K([^\"]+)" "$path")
|
|
if [[ -n "${__mod_idx[$rp_module_id]}" ]]; then
|
|
__INFMSGS+=("Module $path was skipped as $rp_module_id is already used by ${__mod_info[$rp_module_id/path]}")
|
|
return
|
|
fi
|
|
fi
|
|
|
|
source "$path"
|
|
|
|
local var
|
|
for var in rp_module_id rp_module_desc; do
|
|
if [[ -z "${!var}" ]]; then
|
|
echo "Module $module_path is missing valid $var"
|
|
error=1
|
|
fi
|
|
done
|
|
[[ $error -eq 1 ]] && exit 1
|
|
|
|
local flags=($rp_module_flags)
|
|
local flag
|
|
local enabled=1
|
|
|
|
# flags are parsed in the order provided in the module - so the !all flag only makes sense first
|
|
# by default modules are enabled for all platforms
|
|
if [[ "$__ignore_flags" -ne 1 ]]; then
|
|
for flag in "${flags[@]}"; do
|
|
# !all excludes the module from all platforms
|
|
if [[ "$flag" == "!all" ]]; then
|
|
enabled=0
|
|
continue
|
|
fi
|
|
# flags without ! make the module valid for the platform
|
|
if isPlatform "$flag"; then
|
|
enabled=1
|
|
continue
|
|
fi
|
|
# flags with !flag will exclude the module for the platform
|
|
if [[ "$flag" =~ ^\!(.+) ]] && isPlatform "${BASH_REMATCH[1]}"; then
|
|
enabled=0
|
|
continue
|
|
fi
|
|
# enable or disable based on a comparison in the format :\$var:cmp:val or !:\$var:cmp:val
|
|
# eg. :\$__gcc_version:-lt:7 would be evaluated as [[ $__gcc_version -lt 7 ]]
|
|
# this would enable a module if the comparison was true
|
|
# !:\$__gcc_version:-lt:7 would disable if the comparison was true
|
|
|
|
# match and extract the parameters
|
|
if [[ "$flag" =~ ^(\!?):([^:]+):([^:]+):(.+)$ ]]; then
|
|
# enable or disable based on the first parameter (!)
|
|
local e=1
|
|
[[ ${BASH_REMATCH[1]} == "!" ]] && e=0
|
|
# evaluate the comparison
|
|
if eval "[[ ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]} ]]"; then
|
|
enabled=$e
|
|
fi
|
|
continue
|
|
fi
|
|
done
|
|
fi
|
|
|
|
local sections=($rp_module_section)
|
|
# get default section
|
|
rp_module_section="${sections[0]}"
|
|
|
|
# loop through any additional flag=section parameters
|
|
local flag section
|
|
for section in "${sections[@]:1}"; do
|
|
section=(${section/=/ })
|
|
flag="${section[0]}"
|
|
section="${section[1]}"
|
|
isPlatform "$flag" && rp_module_section="$section"
|
|
done
|
|
|
|
# create numerical index for each module id from nunber of added modules
|
|
__mod_idx["$rp_module_id"]="${#__mod_id[@]}"
|
|
__mod_id+=("$rp_module_id")
|
|
__mod_info["$rp_module_id/enabled"]="$enabled"
|
|
__mod_info["$rp_module_id/path"]="$path"
|
|
__mod_info["$rp_module_id/vendor"]="$vendor"
|
|
__mod_info["$rp_module_id/type"]="$type"
|
|
__mod_info["$rp_module_id/desc"]="$rp_module_desc"
|
|
__mod_info["$rp_module_id/help"]="$rp_module_help"
|
|
__mod_info["$rp_module_id/licence"]="$rp_module_licence"
|
|
__mod_info["$rp_module_id/section"]="$rp_module_section"
|
|
__mod_info["$rp_module_id/flags"]="$rp_module_flags"
|
|
|
|
# split module repo into type, url, branch and commit
|
|
if [[ -n "$rp_module_repo" ]]; then
|
|
local repo=($rp_module_repo)
|
|
__mod_info["$rp_module_id/repo_type"]="${repo[0]}"
|
|
__mod_info["$rp_module_id/repo_url"]="${repo[1]}"
|
|
__mod_info["$rp_module_id/repo_branch"]="${repo[2]}"
|
|
__mod_info["$rp_module_id/repo_commit"]="${repo[3]}"
|
|
fi
|
|
}
|
|
|
|
function rp_registerModuleDir() {
|
|
local dir="$1"
|
|
[[ ! -d "$dir" ]] && return 1
|
|
local vendor="$2"
|
|
[[ -z "$vendor" ]] && return 1
|
|
local module
|
|
while read module; do
|
|
rp_registerModule "$module" "$dir" "$vendor"
|
|
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type f -name "*.sh" | sort)
|
|
return 0
|
|
}
|
|
|
|
function rp_registerAllModules() {
|
|
__mod_id=()
|
|
declare -Ag __mod_idx=()
|
|
declare -Ag __mod_info=()
|
|
|
|
local dir
|
|
local type
|
|
local vendor
|
|
while read dir; do
|
|
# get parent folder
|
|
vendor="${dir%/*}"
|
|
# if the folder isn't RetroPie-Setup then get the repo name which will be used for module vendor
|
|
if [[ "$vendor" != "$scriptdir" ]]; then
|
|
vendor="${vendor##*/}"
|
|
else
|
|
vendor="RetroPie"
|
|
fi
|
|
|
|
for type in emulators libretrocores ports supplementary admin; do
|
|
rp_registerModuleDir "$dir/$type" "$vendor"
|
|
done
|
|
done < <(find "$scriptdir"/scriptmodules "$scriptdir"/ext/*/scriptmodules -maxdepth 0 2>/dev/null)
|
|
}
|
|
|
|
function rp_getSectionIds() {
|
|
local section
|
|
local id
|
|
local ids=()
|
|
for id in "${__mod_id[@]}"; do
|
|
for section in "$@"; do
|
|
[[ "${__mod_info[$id/section]}" == "$section" ]] && ids+=("$id")
|
|
done
|
|
done
|
|
echo "${ids[@]}"
|
|
}
|
|
|
|
function rp_isInstalled() {
|
|
local id="$1"
|
|
local md_inst="$rootdir/${__mod_info[$id/type]}/$id"
|
|
[[ -d "$md_inst" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
function rp_isEnabled() {
|
|
local id="$1"
|
|
[[ "${__mod_info[$id/enabled]}" -eq 0 ]] && return 1
|
|
return 0
|
|
}
|
|
|
|
function rp_updateHooks() {
|
|
local function
|
|
local id
|
|
for function in $(compgen -A function _update_hook_); do
|
|
id="${function/_update_hook_/}"
|
|
[[ -n "$id" && "${__mod_info[$id/enabled]}" -eq 1 ]] && rp_callModule "$id" _update_hook
|
|
done
|
|
}
|