summaryrefslogtreecommitdiff
path: root/src/maintenance-tools/parabola-dependents
blob: f601bb3881449ff32afc235c5393619be357256f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/bin/bash

readonly BE_VERBOSE=$( [[ "$1" == '-v' ]] && echo 1 || echo 0 )
# readonly REPOS=( nonprism nonsystemd{-testing,} libre{-testing,}
readonly REPOS=( nonprism{-testing,} nonsystemd{-testing,} libre{-testing,}
                 kernels{-testing,} testing core extra community{-testing,}
                 pcr{-testing,} nonprism-multilib{-testing,} nonsystemd-multilib
                 libre-multilib{-testing,} multilib{-testing,} pcr-multilib{-testing,} )
readonly DUMMY_PKGBUILD='pkgname=%s
pkgver=0.0.0
pkgrel=42
pkgdesc="dummy PKGBUILD for experimentation"
arch=(armv7h i686 x86_64)
url=https://nowhere.man
license=(GPL)
source=(PKGBUILD)
sha256sums=(SKIP)
package() { echo "created '"'\${pkgname}'"' package" ; }'
readonly USAGE='USAGE:
  parabola-dependents [-v] <PACKAGE_NAME>

  List all parabola packages which are dependents of a specified package.

  By default, only first-order dependents are listed,
  with counts of higher-order dependents.

  If <PACKAGE_NAME> does not exist, a dummy package will be created,
  in memory, for pactree to reflect upon.

  Note: this script can take several minutes to complete,
        if the dependency-tree for <PACKAGE_NAME> is large.

  Options:
    -v
      Itemize all higher-order dependents, displaying the dependency-chain.'
readonly INVALID_ARG_MSG="no dependency package specified\n\n${USAGE}"
readonly UNPRIVILEGED_MSG="this script requires super-user privileges"
readonly CBLUE='\033[0;36m'
readonly CRED='\033[0;31m'
readonly CEND='\033[0m'
readonly SEP_CHAR='='
DB_DIR=''   # Init()
CFG_FILE='' # Init()
OPTS=''     # Init()

Ignored=()                   # CollectResults()
Dependents=()                # CollectResults()
declare -A HiorderDependents # CollectResults()


Log() # (log_fmt [args]*)
{
  local log_fmt=$1 ; shift ;
  local args="$@"

  printf "${CBLUE}${log_fmt}${CEND}\n" ${args} >&2
}

LogError() # (source_file func_name line_n)
{
  local source_file="$1"
  local func_name=$2
  local line_n=$3

  printf "${CRED}ERROR: in ${func_name}\n${line_n}: %s${CEND}\n" \
  "$(awk "(( NR == ${line_n} )) { print }" ${source_file})"      >&2
}

Init() # (dep_pkgname)
{
  local dep_pkgname=$1
  local dummy_pkg=${dep_pkgname}-0.0.0-42-$(uname -m).pkg.tar.xz

  readonly DB_DIR="$(su $(logname) -c 'mktemp -d -t parabola-dependents-XXXXXXXXXX')"
  readonly CFG_FILE=${DB_DIR}/pacman-all.conf
  readonly OPTS="--dbpath=${DB_DIR} --config=${CFG_FILE}"

  printf "[options]\nArchitecture = auto\n"                     > ${CFG_FILE}
  for repo in ${REPOS[@]}
  do  printf "[${repo}]\nInclude = /etc/pacman.d/mirrorlist\n" >> ${CFG_FILE}
  done

  Log "updating database ...."
  pacman ${OPTS} -Sy &> /dev/null || true

  # create dummy for missing dependency package
  if   ! pacman ${OPTS} -Ss ^${dep_pkgname}$ &> /dev/null
  then Log "package '${dep_pkgname}' not found - creating dummy"
       cd ${DB_DIR}
       printf "${DUMMY_PKGBUILD}\n" ${dep_pkgname} > ./PKGBUILD
       su $(logname) -c 'makepkg --force &> /dev/null'

       [[ -f ./${dummy_pkg} ]]

       repo-add --new parabola-dependents.db.tar ./${dummy_pkg} &> /dev/null

       Log "dummy package '${dep_pkgname}' created"
       printf "[parabola-dependents]\nServer = file://${DB_DIR}\n"  >> ${CFG_FILE}
  fi
  pacman ${OPTS} -Sy &> /dev/null
}

IsArchRepo() # (repo)
{
  local repo=$1 ; [[ "${repo}" =~ ^(community|core|extra|multilib|testing)$ ]]
}

CollectResults() # (dep_pkgname)
{
  local dep_pkgname=$1
  local dep_chains dep_chain dep_pkg via_pkg repos repo

# TODO: --chain is a custom pactree feature
export PATH="/code/pacman-contrib/src:${PATH}"

  # query database
  Log "searching ...."
  mapfile -t dep_chains < <(pactree ${OPTS} --sync --reverse --unique --chain \
                                    ${dep_pkgname} | sort                     )

  # compile results
  Log "compiling results for (${#dep_chains[@]}) dependencies ...."
  for dep_chain in "${dep_chains[@]}"
  do  dep_pkg=$(sed 's|.*<- ||'                 <<<${dep_chain})
      via_pkg=$(sed 's|.*\] <- \([^ ]*\).*|\1|' <<<${dep_chain})
      repos=$(pacman ${OPTS} -Si ${dep_pkg} | grep Repository | cut -d ':' -f 2 | tr -d ' ')

      for repo in ${repos}
      do  if   IsArchRepo ${repo}
          then Ignored+=(    "${repo}/${dep_pkg}"              )
          else Dependents+=( "${repo}/${dep_pkg} ${dep_chain}" )
               HiorderDependents[${via_pkg}]=$(( ${HiorderDependents[${via_pkg}]} + 1 ))
          fi
      done
  done
}

PrintReport()
{
  local dep_chain is_hiorder_dep via_pkg repo_pkg
  declare -i n_hiorder_deps

  echo
  for dep_chain in "${Dependents[@]}"
  do  if   (( BE_VERBOSE ))
      then echo "${dep_chain/ /${SEP_CHAR}}"
      else is_hiorder_dep=$( (( $(tr '<' '\n' <<<${dep_chain} | wc -l) > 2 )) && echo 1 || echo 0 )

           if ! (( is_hiorder_dep ))
           then via_pkg=$(sed 's|.*\] <- \([^ ]*\).*|\1|' <<<${dep_chain})
                repo_pkg=${dep_chain/ *}
                n_hiorder_deps=$(( ${HiorderDependents[${via_pkg}]} - 1 ))
                echo "${repo_pkg}${SEP_CHAR}(${n_hiorder_deps} higher-order deps)"
           fi
      fi
  done | column --table --separator="${SEP_CHAR}" --table-wrap=2

  echo
  if   (( BE_VERBOSE && ${#Ignored[@]} ))
  then printf "%s${SEP_CHAR}(ignored)\n" "${Ignored[@]}" | column --table --separator="${SEP_CHAR}"
  else echo "(${#Ignored[@]} ignored)"
  fi
}

Cleanup()
{
  if   [[ -d "${DB_DIR}" && "${DB_DIR}" =~ parabola-dependents ]]
  then rm --force --recursive ${DB_DIR} ;
  fi
}


set -o errexit -o errtrace
trap 'Cleanup'                                                   EXIT INT TERM
trap 'LogError "${BASH_SOURCE[0]}" "${FUNCNAME[0]}" "${LINENO}"' ERR


(( $BE_VERBOSE )) && shift                           || true
(( ! EUID      )) || ! echo -e "${UNPRIVILEGED_MSG}" || exit 1
(( $#          )) || ! echo -e "${INVALID_ARG_MSG}"  || exit 1


LANG=C
Init           $1
CollectResults $1
PrintReport