#!/usr/bin/env bash # # checkversion: package update checking tool # # Written by Kevin MacMartin # Released under the MIT license # # Requirements: # pacaur # vercmp # archversion (environment config patched version) # # archversion_conf: config file with archversion entries for non-VCS packages # develversion_conf: config file listing VCS packages to check # noversion_conf: text file listing packages that won't be checked # # specialpkg_check(): Contained in this script, custom version checks go in this function # cd "${0%/*}" || exit script_directory="$PWD" # Directory containing this script script_name="${0//*\/}" # Name of this script package_rootdir="$(readlink -f "$script_directory"/..)" # Directory containing a collection of packages contained in folders archversion_conf="$script_directory/archversion.conf" # A config file containing Archversion checks for non-VCS packages develversion_conf="$script_directory/develversion.conf" # A file containing a list of VCS packages to check noversion_conf="$script_directory/noversion.txt" # A file containing a list of packages that shouldn't be checked temp_directory="/tmp/$script_name" # The root folder containing any temporary files used temp_config="$temp_directory/upversion.tmp.conf" # Location to create temp archversion configs package_cache="$temp_directory/.archversion.cache" # Location to create the archversion cache file # Set all the colour variable values blank for when this script outputs to a pipe unset c_blue c_white c_yellow c_grey c_red c_green c_reset # Set the terminal colours to use when this script outputs to stdout [[ -t 1 ]] && { c_blue=$'\e[1;34m' # BLUE c_white=$'\e[1;37m' # WHITE c_grey=$'\e[1;30m' # DARK GREY c_yellow=$'\e[1;33m' # YELLOW c_red=$'\e[1;31m' # RED c_green=$'\e[1;32m' # GREEN c_reset=$'\e[0m' # DISABLES COLOUR } # SPECIALPKG CHECK: function for custom version check functions function specialpkg_check() { # Enter the package root directory cd "$package_rootdir" || exit # PKGVER CHECK: terminfo-italics specialpkg=terminfo-italics upstream_version=$(pacman -Si ncurses \ | grep Version \ | sed 's|^[^:]*:\ ||;s|-.*$||') pkgver='' eval "$(grep -E '^\s*pkgver\s*=' $specialpkg/PKGBUILD)" vercmp_check "$specialpkg" "$upstream_version" "$pkgver" # Return to the script folder cd "$script_directory" || exit } # HELPER FUNCTION: Output readible results of a version comparison function vercomp_display() { printf '%s\n' "${c_blue}[$c_white$1$c_blue]$c_reset ${c_yellow}up: $2$c_reset $c_blue|$c_reset $3" } # HELPER FUNCTION: Compares versions and handles accordingly function vercmp_check() { package="$1" upstream_version="$2" package_version="$3" version_comparison=$(vercmp "$upstream_version" "$package_version") if [ "$version_comparison" -gt 0 ]; then # Upstream > Package (New Version) vercomp_display "$package" "$upstream_version" "${c_red}aur: $package_version$c_reset" elif [ "$version_comparison" -lt 0 ]; then # Upstream < Package (Error) [[ "$only_newpkgs" = '0' ]] \ && vercomp_display "$package" "$upstream_version" "${c_yellow}aur: $package_version$c_reset" else # Upstream = Package (Up to Date) [[ "$only_newpkgs" = '0' ]] \ && vercomp_display "$package" "$upstream_version" "${c_green}aur: $package_version$c_reset" fi } # archversion_conf CHECK function archversion_check() { # Fail if the archversion config file is missing [[ ! -f "$archversion_conf" ]] && { printf '%s\n' "${c_red}ERROR$c_reset: $archversion_conf is missing" >&2 exit 1 } # Define the arguments for archversion, then add '--debug' if $archversion_debug is set archversion_command='check' [[ "$archversion_debug" = '1' ]] \ && archversion_command="--debug $archversion_command" # Create a vanilla archversion cache file if one doesn't already exist [[ ! -f "$package_cache" ]] \ && printf '%s\n' '{"downstream": {}, "compare": {}}' > "$package_cache" # Run for each package defined in the $archversion_conf file while read -r pkg; do # Write (or write over) the config file with a template for everything above packages sed 's|^\s*#\s*||;/\[\s*DEFAULT\s*\]/,$!d;/^$/q' "$archversion_conf" \ > "$temp_config" # Define $package_definition as an empty archversion.conf template then add the $pkg entry from $archversion_conf if grep -qE "\[\s*$pkg\s*\]" "$archversion_conf"; then package_definition="$(sed 's|^\s*#\s*||;/\[\s*'"$pkg"'\s*\]/,$!d;/^$/q' "$archversion_conf")" else package_definition="[$pkg]" fi # Add the definition to the package printf '%s\n' "$package_definition" \ >> "$temp_config" # Grab the simple archversion output for parsing archversion_output=$(CONFIG_PACKAGES="$temp_config" CACHE_PACKAGES="$package_cache" archversion $archversion_command) upstream_version=$(grep -oE 'up: [^ ]*' <<< "$archversion_output" \ | sed 's|up: ||') package_version=$(grep -oE 'aur: [^ ]*' <<< "$archversion_output" \ | sed 's|aur: ||' \ | sed 's|^[0-9][0-9]*:||') # Compare versions and handle accordingly vercmp_check "$pkg" "$upstream_version" "$package_version" # Add a blank line after each package when running debug for easier parsing [[ "$archversion_debug" = '1' ]] && printf '\n' # Remove the tmp config [[ -f "$temp_config" ]] && rm "$temp_config" done < <(grep -vE '\[DEFAULT\]' "$archversion_conf" | grep -E '^\s*\[[^]]*\]' | grep -oE '[^][]*') } # develversion_conf CHECK function develversion_check() { # Fail if the develversion config file is missing [[ ! -f "$develversion_conf" ]] && { printf '%s\n' "${c_red}ERROR$c_reset: $develversion_conf is missing" >&2 exit 1 } # Check each package in $develversion_conf that exists in $package_rootdir while read -r pkg; do if [[ -d "$package_rootdir/$pkg" ]]; then # Find the current pkgver() of the package in the AUR package_version=$(pacaur -i "$pkg" \ | sed -r 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' \ | grep -oE '^Version[^:]*: *[^ ]*' \ | sed 's|^[^:]*: ||;s|-[0-9]*$||') pkgbuild_file="$package_rootdir/$pkg/PKGBUILD" # Exit and skip this package if either its folder or the PKGBUILD it should contain are missing [[ ! -f "$pkgbuild_file" ]] && { if [[ ! -d "$package_rootdir/$pkg" ]]; then printf '%s\n' "${c_blue}[$c_white$pkg$c_blue]$c_reset ${c_red}ERROR$c_reset: $pkg does not exist in $package_rootdir" >&2 else printf '%s\n' "${c_blue}[$c_white$pkg$c_blue]$c_reset ${c_red}ERROR$c_reset: $package_rootdir/$pkg does not contain a PKGBUILD" >&2 fi continue } # Exit and skip this package if a pkgver() function doesn't exist (ie: not VCS) grep -qE 'pkgver\(' "$pkgbuild_file" || { printf '%s\n' "${c_blue}[$c_white$pkg$c_blue]$c_reset ${c_red}ERROR$c_reset: package doesn't contain a pkgver() function" >&2 continue } # Delete the temporary build folder if it already exists [[ -d "$temp_directory/build" ]] && rm -rf "$temp_directory/build" # Create and enter the temporary build folder, exiting with an error if this fails if install -d "$temp_directory/build"; then cd "$temp_directory/build" || exit else printf '%s\n' "${c_red}ERROR$c_reset: Failure to create and enter the temporary build folder @ $temp_directory/build" >&2 exit 1 fi # Link all folders in the package directory except pkg+src to avoid re-cloning repos each check for dir in $(find "$package_rootdir/$pkg" -maxdepth 1 -mindepth 1 -type d | grep -vE '(src|pkg)'); do ln -s "$dir" "$temp_directory/build" done # Copy the package's PKGBUILD to the temporary build folder with its functions stripped out sed '/^.*\(\).*{[^}]*$/,/^[^{]]*}/d' "$pkgbuild_file" \ | grep -vE '^\s*install\s*=' \ > PKGBUILD # Add the package's pkgver() function from the original PKGBUILD into the copy sed '/pkgver(/,$!d' "$pkgbuild_file" | sed '/^\s*}\s*$/q' >> PKGBUILD # Reset the values we'll be using then source the new PKGBUILD unset pkgname epoch pkgver pkgrel source PKGBUILD [[ ! "$pkgname" = "$pkg" ]] && { printf '%s\n' "${c_blue}[$c_white$pkg$c_blue]$c_reset ${c_red}ERROR$c_reset: This pkgname for this package is $pkgname" >&2 continue } # Create a blank package function (or a blank one for each split package) if [[ "${#pkgname[*]}" = 1 ]]; then # If this is not a split package, add one package() to the PKGBUILD printf '%s\n%s\n%s\n' 'package() {' ' return 0' '}' >> PKGBUILD else # If this is a split package, add one package() per split to the PKGBUILD for name in "${pkgname[@]}"; do printf '%s%s\n%s\n%s\n' "package_$name" '() {' ' return 0' '}' >> PKGBUILD done fi # Unset all checksums then add each VCS source and an associated 'SKIP' checksum to the PKGBUILD printf '%s\n' 'unset md5sums sha1sums sha256sums sha384sums sha512sums' >> PKGBUILD source_array='source=(' sha512sum_array='sha512sums=(' printf '\n' >> PKGBUILD for src in "${source[@]}"; do grep -qE '^\s*(bzr|csv|git|hg|darcs|svn)[^a-zA-Z0-9]' < <(sed 's|^[^:]*::||' <<< "$src") && { source_array="$source_array '$src'" sha512sum_array="$sha512sum_array 'SKIP'" } done sed 's|( |(|' <<< "${source_array})" >> PKGBUILD sed 's|( |(|' <<< "${sha512sum_array})" >> PKGBUILD # Update sources with makepkg and compare the local pkgver against the one in the AUR makepkg_output=$(makepkg -od 2>&1) if [[ $? = 0 ]]; then # Calculate the full package version, including epoch (if applicable), pkgver and pkgrel unset epoch pkgver pkgrel eval "$(grep -E '^\s*(epoch|pkgver|pkgrel)\s*=' PKGBUILD)" [[ -n "$epoch" ]] && epoch="$epoch:" upstream_version="$epoch$pkgver-$pkgrel" # Compare versions and handle accordingly vercmp_check "$pkg" "$upstream_version" "$package_version" else # Exit with a failure and display $makepkg_output if makepkg fails printf '%s\n%s\n' "${c_red}ERROR$c_reset: Failed to update sources" "$makepkg_output" >&2 fi else # Display an error if the package can't be found in the package root directory printf '%s\n' "${c_red}ERROR$c_reset: Failed to find the package $pkg in $package_rootdir" >&2 fi done < <(grep -vE '^#' "$develversion_conf") # Move back to $script_directory cd "$script_directory" || exit } # check_missingpkgsPKG CHECK function missingpkg_check() { # Create lists of archversion, develversion, specialversion and noversion packages specialversion_packages='terminfo-italics' noversion_packages=$(sed 's|\[||;s|\]||;s|\s*#.*$||' "$noversion_conf") develversion_packages=$(grep -vE '^\s*#' "$develversion_conf") archversion_packages=$(grep -vE '^\s*#' "$archversion_conf" \ | grep -v '[DEFAULT]' \ | grep -E '^\s*\[[^]]*\]' \ | sed 's|\[||;s|\]||') # Create a list of packages in the package root directory that aren't in any of the above lists check_missing_pkgs=$( cd "$package_rootdir" || exit for pkg in *; do [[ -f "$pkg/PKGBUILD" ]] && { _pkg=$(sed 's|.*\/||' <<< "$pkg") ! grep -qE "^$_pkg$" <<< "$archversion_packages" \ && ! grep -qE "^$_pkg$" <<< "$develversion_packages" \ && ! grep -qE "^$_pkg$" <<< "$specialversion_packages" \ && ! grep -qE "^$_pkg$" <<< "$noversion_packages" \ && printf '%s\n' "$_pkg" } done cd "$script_directory" || exit ) # Display information about any packages missing from all the package lists printf '%s' "${c_blue}[${c_white}missing-packages$c_blue]$c_reset: " if [[ -n "$check_missing_pkgs" ]]; then # Display how many packages are missing printf '%s\n' "$c_red$(wc -l <<< "$check_missing_pkgs")$c_reset" # Display the list of missing packages printf '%s\n' "$check_missing_pkgs" else # Display 0 to show that there are no packages missing printf '%s\n' "${c_green}0$c_reset" fi } # HELP + EXIT function showhelp_exit(){ printf '%s\n\n' "Usage: $script_name [OPTION(S)]" printf '%s\n' "Version Options:" printf '%s\n' " -b|b $c_grey|$c_reset include debugging information with archversion checks" printf '%s\n\n' " -n|n $c_grey|$c_reset only display packages that have a new version available" printf '%s\n' "Check Options:" printf '%s\n\n' " -m|m $c_grey|$c_reset find packages missing from all the version check configs" printf '%s\n' "Help Options:" printf '%s\n' " -h|h $c_grey|$c_reset display this help output" exit "$1" } # HELPER FUNCTION: Set settings variables depending on the arguments pasted to this function function paramparse(){ case "$1" in -b|b) archversion_debug=1 ;; -n|n) only_newpkgs=1 ;; -m|m) check_missingpkgs=1 ;; -h|h|--help) showhelp_exit 0 ;; *) printf '%s\n\n' "${c_red}ERROR$c_reset: invalid argument '$param'" >&2 showhelp_exit 1 ;; esac } # Set all setting variables off before parsing for arguments check_missingpkgs=0 archversion_debug=0 only_newpkgs=0 # Parse command-line arguments for param in "$@"; do if grep -qE '^-[a-z][a-z]' <<< "$param"; then # Parse the argument character by character while read -r char; do paramparse "$char" done < <(grep -o '.' <<< "$param" | grep -v '-') else # Parse the whole argument at once paramparse "$param" fi done # Run the missing packages function then exit if configured to do so [[ "$check_missingpkgs" = '1' ]] && { missingpkg_check exit 0 } # Initialize the temp folder [[ -d "$temp_directory" ]] && rm -rf "$temp_directory" install -d "$temp_directory" archversion_check develversion_check specialpkg_check # Cleanup the temp folder [[ -d "$temp_directory" ]] && rm -rf "$temp_directory"