aboutsummaryrefslogtreecommitdiff
path: root/bin/ThirdPartyInstaller
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ThirdPartyInstaller')
-rwxr-xr-xbin/ThirdPartyInstaller508
1 files changed, 508 insertions, 0 deletions
diff --git a/bin/ThirdPartyInstaller b/bin/ThirdPartyInstaller
new file mode 100755
index 0000000..131a31e
--- /dev/null
+++ b/bin/ThirdPartyInstaller
@@ -0,0 +1,508 @@
+#!/bin/bash
+
+source ScriptFunctions
+Import File
+Import GoboLinux
+Import Log
+Import OptionParser
+
+### Options ###################################################################
+
+scriptDescription="Install RPM and DEB packages on GoboLinux."
+scriptCredits="Copyright (C) Lucas C. Villa Real, 2016,2017 - Released under the GNU GPL."
+helpOnNoArguments=yes
+scriptUsage="<file.rpm> [file.rpm]"
+scriptExample="xispita-2.0.3-1.x86_64.rpm"
+
+Add_Option_Entry "n" "app-name" "Override program name"
+Add_Option_Entry "e" "version-number" "Override program version number"
+Add_Option_Entry "l" "symlink" "If symlinks should be created and wether they should be forced on conflicts." "yes" "yes no force"
+Add_Option_Boolean "W" "no-web" "Do not search the web to resolve dependencies."
+Parse_Options "$@"
+
+### Functions #################################################################
+
+function fetch_package() {
+ local inputfile="$1"
+ local filename=$(basename "$inputfile")
+ if Is_URL "$inputfile"
+ then
+ if wget --help | grep -q "no-check-certificate"
+ then wget_cmd="wget --no-check-certificate"
+ else wget_cmd="wget"
+ fi
+ Quiet ${wget_cmd} -c "${inputfile}" -O "${goboTemp}/${filename}" || Die "Error downloading package."
+ echo "${goboTemp}/${filename}"
+ else
+ echo "$inputfile"
+ fi
+}
+
+function uncompress_package() {
+ local inputfile="$1"
+ thirdparty_uncompress "$inputfile"
+}
+
+function determine_flattening_level() {
+ local rootdirs="bin$\|sbin$\|lib$\|lib64$\|libexec$\|include$\|share$"
+ local filenames=$(for i in "${inputfiles[@]}"; do thirdparty_filenames $i; done)
+
+ Log_Verbose "$filenames"
+ if echo "$filenames" | grep -q "/opt/$rootdirs"
+ then
+ # 1-level /opt hierarchy
+ echo "1"
+ elif echo "$filenames" | grep -q "/opt/[^/]*/$rootdirs"
+ then
+ # 2-level /opt hierarchy
+ echo "2"
+ elif echo "$filenames" | grep -q "/opt"
+ then
+ # Get the longest common prefix among the $filenames list that starts
+ # with "/opt" and then determine how many path levels we have there.
+ local commonprefix=$(echo "$filenames" | grep "/opt" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1\n\1/;D')
+ local numslashes=$(echo "$commonprefix" | grep -o "/" | wc -l)
+ let numslashes=numslashes-1
+ if [ "$numslashes" = 1 ]
+ then
+ Log_Verbose "Package seems to have a 1-level /opt structure"
+ echo "1"
+ elif [ "$numslashes" = 2 ]
+ then
+ Log_Verbose "Package seems to have a 2-level /opt structure"
+ echo "2"
+ else
+ Log_Error "Could not determine this package's /opt structure, assuming 2-level"
+ echo "2"
+ fi
+ fi
+}
+
+function flatten_package() {
+ local inputfile="$1"
+
+ Log_Normal "Flattening directory structure."
+
+ function create_opt_links() {
+ local vendordir="$1"
+ local unmanagedopt="Resources/Unmanaged/opt"
+ for pkgdir in $(basename --multiple ./opt/$vendordir/*)
+ do
+ mkdir ${verbose} -p "${unmanagedopt}/$vendordir/$pkgdir"
+ if [ -h "/opt/$vendordir/$pkgdir" ]
+ then
+ Log_Error "Too many programs are attempting to populate /opt/$vendordir/$pkgdir"
+ Log_Error "This feature is not supported by ThirdPartyInstaller at this time."
+ continue
+ fi
+ for optfile in $(basename --multiple ./opt/$vendordir/$pkgdir/*)
+ do
+ if [ ! -e "${unmanagedopt}/$vendordir/$pkgdir/$optfile" ]
+ then
+ ln ${verbose} -fs $target/$optfile "${unmanagedopt}/$vendordir/$pkgdir/$optfile"
+ fi
+ done
+ done
+ }
+
+ function flatten_opt_1_level() {
+ # Flatten 1-level dir: opt/pkgname/{bin,sbin,...}
+ cp ${verbose} -a ./opt/*/* .
+ create_opt_links ""
+ realpath ./opt/* | sed "s,$(realpath $PWD),,g" >> Resources/UnmanagedFiles
+ }
+
+ function flatten_opt_2_levels() {
+ # Flatten 2-levels dir: opt/vendorname/pkgname/{bin,sbin,...}
+ cp ${verbose} -a ./opt/*/*/* .
+ for vendordir in $(basename --multiple ./opt/*)
+ do
+ create_opt_links "$vendordir"
+ done
+ realpath ./opt/*/* | sed "s,$(realpath $PWD),,g" >> Resources/UnmanagedFiles
+ }
+
+ if [ -d "./usr" ]
+ then
+ cp ${verbose} -a ./usr/* .
+ rm -rf -- ./usr
+ fi
+ if [ -d "./etc" ]
+ then
+ mkdir -p Resources/Defaults/Settings
+ mv ${verbose} ./etc/* Resources/Defaults/Settings
+ rm -rf -- ./etc
+ fi
+ if [ -d "./lib64" ]
+ then
+ Quiet rmdir lib
+ if [ ! -d "./lib" ]
+ then mv ./lib64 lib
+ else cp -a ./lib64/* ./lib/
+ fi
+ rm -rf ./lib64
+ fi
+ if [ -d "./var" ]
+ then
+ mkdir -p Resources/Unmanaged/$goboVariable
+ find ./var | sed "s,./var,$goboVariable,g" >> Resources/UnmanagedFiles
+ mv ${verbose} ./var/* Resources/Unmanaged/$goboVariable
+ rm -rf -- ./var
+ fi
+ if [ -d "./opt" ]
+ then
+ # Prevent the creation of backlinks to directories that would be pruned later on
+ find share -type d | xargs rmdir -p --ignore-fail-on-non-empty
+ rmdir * 2> /dev/null
+ mkdir -p Resources/Unmanaged/opt
+ if [ "$flatteninglevel" = "1" ]
+ then
+ Log_Verbose "Flattening 1-level /opt directory"
+ flatten_opt_1_level
+ elif [ "$flatteninglevel" = "2" ]
+ then
+ Log_Verbose "Flattening 2-level /opt directory"
+ flatten_opt_2_levels
+ fi
+ rm -rf -- ./opt
+ fi
+ if [ -e Resources/UnmanagedFiles ]
+ then
+ # If multiple RPM/DEB files are being merged, then ensure we have no dups
+ cat Resources/UnmanagedFiles | sort -n | uniq > x && mv x Resources/UnmanagedFiles
+ fi
+ rmdir * 2> /dev/null
+}
+
+function is_inputfile() {
+ local dependency="$1"
+ local pkgname=$(echo "$dependency" | cut -d'(' -f1 | awk {'print $1'})
+ printf "%s\n" "${inputnames[@]}" | grep -q "^${pkgname}$" && return 0
+ return 1
+}
+
+function populate_dependencies() {
+ local inputfile="$1"
+ thirdparty_dependencies "$inputfile" | while read dependency
+ do
+ if echo "$dependency" | grep -q "^/"
+ then
+ depinfo=$(take_dependency_from_path "$inputfile" "$dependency" "")
+ if [ "$depinfo" ]
+ then echo "$depinfo"
+ else echo "# Unresolved path-based dependency: $dependency"
+ fi
+ elif echo "$dependency" | grep -q "^lib.*\.so*\|.*\.so\.*"
+ then
+ libname=$(echo "$dependency" | cut -d'(' -f1)
+ wantedsymbol=$(echo "$dependency" | cut -d'(' -f2 | cut -d')' -f1)
+ depinfo=$(take_dependency_from_path "$inputfile" "$goboLibraries/$libname" "$wantedsymbol")
+ if [ "$depinfo" ]
+ then echo "$depinfo"
+ else echo "# Unresolved path-based library dependency: $dependency"
+ fi
+ elif is_basic_symbol "$dependency"
+ then
+ Log_Verbose "Skipping basic symbol: $dependency"
+ elif is_inputfile "$dependency"
+ then
+ Log_Verbose "Skipping dependency passed as input file: $dependency"
+ else
+ depinfo=$(lookup_pkgname "$dependency")
+ if [ "$depinfo" ]
+ then echo "$depinfo"
+ else echo "# Unresolved dependency: $dependency"
+ fi
+ fi
+ done
+}
+
+function populate_resources() {
+ local inputfile="$1"
+ local arch=$(thirdparty_arch "$inputfile")
+ local description=$(thirdparty_description "$inputfile")
+ local release=$(thirdparty_release "$inputfile")
+ local distro=$(thirdparty_distribution "$inputfile")
+
+ Log_Normal "Populating Resources."
+ mkdir -p Resources
+
+ if [ "$arch" ]
+ then echo "$arch" > Resources/Architecture
+ else echo "$(uname -m)" > Resources/Architecture
+ fi
+ echo "$release" > Resources/Revision
+
+ # Note that we never truncate neither Resources/PackageSource nor
+ # Resources/Description or Resources/Dependencies. This is to
+ # enable the installation of multiple RPM/DEB files under the same
+ # /Programs entry while keeing metadata of all the original files
+ # around.
+
+ [ -e Resources/PackageSource ] && echo >> Resources/PackageSource
+ echo "[File] $(basename $inputfile)" >> Resources/PackageSource
+ echo "[Distribution] $distro" >> Resources/PackageSource
+
+ if [ "$description" ]
+ then
+ [ -e Resources/Description ] && echo "" >> Resources/Description
+ echo "[Name] $(thirdparty_name $inputfile)" >> Resources/Description
+ echo "[Summary] $(thirdparty_summary $inputfile)" >> Resources/Description
+ echo "[License] $(thirdparty_license $inputfile)" >> Resources/Description
+ echo "[Description] $(thirdparty_description $inputfile)" >> Resources/Description
+ echo "[Homepage] $(thirdparty_url $inputfile)" >> Resources/Description
+ fi
+
+ Log_Normal "Processing dependencies."
+ alldeps=$(mktemp ThirdPartyInstaller.XXXXXXXXXX)
+ populate_dependencies "$inputfile" >> Resources/Dependencies
+ cat Resources/Dependencies | sort -n | uniq > "$alldeps"
+ cat "$alldeps" > Resources/Dependencies
+ rm -f -- "$alldeps"
+}
+
+function lookup_symbol() {
+ local depname="$1"
+ local testversion="$2"
+ local arch="$3"
+ local symbol="$4"
+ local testarch=$(cat "$goboPrograms/$depname/$testversion/Resources/Architecture" 2> /dev/null)
+
+ if [ "$testarch" ] && [ "$testarch" = "$arch" ]
+ then
+ Log_Verbose "Looking for symbol $symbol on $goboPrograms/$depname/$testversion/$path"
+ if nm "$goboPrograms/$depname/$testversion/$path" 2> /dev/null | grep --max-count=1 -q "$symbol"
+ then
+ Log_Verbose "Match: $depname $testversion"
+ echo "$depname $testversion"
+ return 0
+ fi
+ fi
+ return 1
+}
+
+function take_dependency_from_path() {
+ # TODO since $2 may come in the form '/usr/lib/libfoo >= version', we have to pick the
+ # path from the first part of the string. Right now we're using awk to get it, but that
+ # will not work if the dependency path contains spaces.
+
+ local inputfile="$1"
+ local originalpath=$(echo "$2" | awk '{print $1}')
+ local path=$(echo "$originalpath" | sed 's,/usr,,g')
+ local symbol="$3"
+ local fullpath="$(readlink -f $path)"
+ local arch=$(thirdparty_arch "$inputfile")
+ local distro=$(thirdparty_distribution "$inputfile")
+
+ local depname=
+ local depversion=
+
+ if echo "$fullpath" | grep -q "^${goboPrograms}"
+ then
+ # If given, we search for the presence of @symbol on the given target file.
+ # We iterate over different installations of the same program looking for
+ # that symbol. If none of the installations have it, we fallback to printing
+ # the dependency currently linked on /System/Index.
+ #
+ # Note that when iterating over installed programs we skip those entries whose
+ # Resources/Architecture do not match the output of $(rpminfo --arch).
+
+ depname=$(echo "$fullpath" | cut -d/ -f3)
+ depversion=$(echo "$fullpath" | cut -d/ -f4)
+ if [ "$symbol" ]
+ then
+ for testversion in $(ls $goboPrograms/$depname/ | grep -v "Settings\|Variable\|Current")
+ do
+ lookup_symbol "$depname" "$testversion" "$arch" "$symbol" && return 0
+ done
+ fi
+
+ Log_Verbose "Fallback: $depname $depversion"
+ echo "$depname $depversion"
+ else
+ # We have a path, but we don't have a link to that file under /System/Index.
+ # Our first attempt is to search over the list of installed programs anyhow,
+ # because some programs may not be currently activated.
+
+ for fullpath in $(ls $goboPrograms/*/*/$path 2> /dev/null | grep -v "Current")
+ do
+ depname=$(echo "$fullpath" | cut -d/ -f3)
+ testversion=$(echo "$fullpath" | cut -d/ -f4)
+ [ -z "$depversion" ] && depversion="$testversion"
+ Log_Verbose "Looking for symbol on candidate file $candidate ($depname, $testversion)"
+ lookup_symbol "$depname" "$testversion" "$arch" "$symbol" && return 0
+ done
+
+ # We don't have a match. If we have a file name that satisfies the path but
+ # that doesn't contain the requested symbol, we simply return that path.
+
+ if [ "$depname" ] && [ "$depversion" ]
+ then
+ echo "$depname $depversion"
+ return 0
+ fi
+
+ # We don't have a matching filename under /System/Index nor under /Programs/*/*.
+ # What we do now is to query remote RPM/DEB databases to find which package hosts the
+ # dependency file.
+
+ if ! Boolean "no-web"
+ then
+ Log_Normal "Searching the remote $(thirdparty_backend) database for the package hosting $originalpath"
+ depname=$(third_party_search_remotedb "$originalpath" "$arch" "$distro")
+ if [ "$depname" ]
+ then
+ # TODO: we could now lookup the GoboLinux recipe store to find whether we
+ # have it or not
+ echo "$(GuessProgramCase $depname)"
+ return 0
+ fi
+ fi
+ fi
+}
+
+function lookup_pkgname() {
+ local dependency="$1"
+ local pkgname=$(echo "$dependency" | cut -d'(' -f1)
+
+ # GoboLinux doesn't have "devel" packages like most mainstream distros do
+ local goboname=$(echo "$pkgname" | sed 's,-devel$,,g')
+
+ # Do we have a GoboLinux package installed with a matching name?
+ for testname in $(ls $goboPrograms/)
+ do
+ # Case-insensitive comparison (requires Bash 4)
+ if [ "${testname,,}" = "${pkgname,,}" ] || [ "${testname,,}" = "${goboname,,}" ]
+ then
+ echo "$testname" && return 0
+ fi
+ done
+
+ # Query the GoboLinux recipe store.
+ if ! Boolean "no-web"
+ then
+
+ local recipeurl=$(FindPackage -t recipe "${pkgname}" || FindPackage -t recipe "${goboname}")
+ if [ "$recipeurl" ]
+ then
+ # TODO we're potentially discarding the wanted version(s) of the given dep
+ echo "$(basename $recipeurl | sed 's,\(.*\)--.*--.*,\1,g')" && return 0
+ fi
+ fi
+
+ # TODO Query the remote RPM database for $pkgname
+
+ # TODO Query the remote DEB database for $pkgname
+
+ return 1
+}
+
+function is_basic_symbol() {
+ local dependency="$1"
+ echo "$dependency" | grep -q "^rtld(" && return 0
+ return 1
+}
+
+function deduce_program_name() {
+ local inputfile="$1"
+ local name=$(thirdparty_name "$inputfile")
+ echo $name
+}
+
+function prepare_program_entry() {
+ local inputfile="$1"
+
+ if [ ${#inputfiles[@]} -gt 1 ] && Is_Entry "app-name" && [ ! -z "$programname" ]
+ then
+ # We have already prepared this program's entry on /Programs
+ return
+ fi
+ if Is_Entry "app-name"
+ then programname=$(Entry "app-name")
+ else programname="$(deduce_program_name $inputfile)"
+ fi
+ if Is_Entry "version-number"
+ then programversion=$(Entry "version-number")
+ else programversion=$(printf "%s_%s" $(thirdparty_version "$inputfile") $(thirdparty_release "$inputfile"))
+ fi
+
+ # Prepare /Programs tree and update program name (PrepareProgram may have changed its case)
+ PrepareProgram -t "$programname" "$programversion"
+ programname=$(ls $goboPrograms/ | grep -i "^${programname}$")
+ target="$goboPrograms/$programname/$programversion"
+}
+
+### Operation #################################################################
+
+Is_Writable "${goboPrograms}" || Verify_Superuser
+
+if Boolean "verbose"
+then verbose="--verbose"
+else verbose=
+fi
+
+# The inputfiles array holds the full path of all RPM/DEB input files
+# The inputnames array holds the package name of all RPM/DEB input files
+inputfiles=()
+inputnames=()
+rpmcount=0
+debcount=0
+eval `Args_To_Array inputfiles_`
+for entry in "${inputfiles_[@]}"
+do
+ inputfiles+=( "$(readlink -f ${entry} || echo ${entry})" )
+ inputnames+=( "$(rpminfo --name $entry)" )
+ echo "$entry" | grep -qi "\.deb" && let debcount=debcount+1
+ echo "$entry" | grep -qi "\.rpm" && let rpmcount=rpmcount+1
+done
+
+# Sanity checks, then import the backend to handle the package format
+if [ $rpmcount -gt 0 ] && [ $debcount -gt 0 ]
+then
+ Die "Error: cannot handle both RPM and DEB files in a single shot."
+elif [ $rpmcount -gt 0 ]
+then
+ Import RPM
+elif [ $debcount -gt 0 ]
+then
+ Import DEB
+fi
+
+# These will be set by prepare_program_entry()
+unset programname
+unset programversion
+unset target
+
+# Determine the flattening level of /opt. In other words, whether
+# we have something like:
+# /opt/pkgname/{bin,sbin...} (1-level), or
+# /opt/vendorname/pkgname/{bin,sbin...} (2-level)
+
+flatteninglevel=$(determine_flattening_level)
+
+# Installation pipeline
+for entry in "${inputfiles[@]}"
+do
+ Log_Normal "Processing $(basename $entry)"
+ inputfile=$(fetch_package "$entry")
+ prepare_program_entry "$inputfile"
+ Quiet pushd "$target" || Die "Could not enter $target"
+
+ uncompress_package "$inputfile"
+ flatten_package "$inputfile"
+ populate_resources "$inputfile"
+
+ Quiet popd
+ Is_URL "$entry" && rm -f -- "$inputfile"
+done
+
+# Symlinking
+if [ $(Entry "symlink") = "no" ]
+then
+ Log_Normal "Done."
+ exit 0
+fi
+
+[ -d "$target/Resources/Defaults/Settings" ] && UpdateSettings "$programname" "$programversion"
+SymlinkProgram "$programname" "$programversion"
+Log_Normal "Done."