OSDN Git Service

dc1025e123f48bc9bfafda9afb79cb6be73c1326
[alterlinux/wfa.git] / wfa
1 #!/usr/bin/env bash
2
3 set -eu
4
5 msgdebug=false
6 nocolor=false
7 debug=false
8 pacman_args=""
9 operation="none"
10 force_aur=false
11 noconfirm=false
12
13 wfa_version="0.1"
14 aururl="https://aur.archlinux.org/"
15 pacman_config="/etc/pacman.conf"
16
17 # メッセージ出力の制御
18 # https://github.com/FascodeNet/alterlinux/blob/dev/tools/msg.sh の変数名にアンダーバーを追加し関数化
19 msg() {
20     local OPTIND OPTARG arg
21
22     local _appname="msg.sh"
23     local _bash_debug=false
24     local _nocolor=false
25     local _echo_opts=""
26     local _message=""
27     local _msg_type="info"
28     local _msg_label=""
29     local _label_space="7"
30     local _adjust_chr=" "
31     local _customized_label=false
32     local _customized_label_color=false
33     local _nolabel=false
34     local _noappname=false
35     local _noadjust=false
36     local _output="stdout"
37
38     _help() {
39         echo "usage ${0} [option] [type] [message]"
40         echo
41         echo "Display a message with a colored app name and message type label"
42         echo
43         echo " General type:"
44         echo "    info                      General message"
45         echo "    warn                      Warning message"
46         echo "    error                     Error message"
47         echo "    debug                     Debug message"
48         echo
49         echo " General options:"
50         echo "    -a [name]                 Specify the app name"
51         echo "    -c [character]            Specify the character to adjust the label"
52         echo "    -l [label]                Specify the label."
53         echo "    -n | --nocolor            No output colored output"
54         echo "    -o [option]               Specify echo options"
55         echo "    -r [color]                Specify the color of label"
56         echo "    -s [number]               Specifies the label space."
57         echo "    -x | --bash-debug         Enables output bash debugging"
58         echo "    -h | --help               This help message"
59         echo
60         echo "         --nolabel            Do not output label"
61         echo "         --noappname          Do not output app name"
62         echo "         --noadjust           Do not adjust the width of the label"
63     }
64
65     while getopts "a:c:l:no:r:s:xh-:" arg; do
66         case ${arg} in
67                 a) 
68                     _appname="${OPTARG}"
69                     ;;
70                 c) 
71                     _adjust_chr="${OPTARG}"
72                     ;;
73                 l) 
74                     _customized_label=true
75                     _msg_label="${OPTARG}"
76                     ;;
77                 n)
78                     _nocolor=true
79                     ;;
80                 o)
81                     _echo_opts="${OPTARG}"
82                     ;;
83                 r)
84                     _customized_label_color=true
85                     case ${OPTARG} in
86                         "black")
87                             _labelcolor="30"
88                             ;;
89                         "red")
90                             _labelcolor="31"
91                             ;;
92                         "green")
93                             _labelcolor="32"
94                             ;;
95                         "yellow")
96                             _labelcolor="33"
97                             ;;
98                         "blue")
99                             _labelcolor="34"
100                             ;;
101                         "magenta")
102                             _labelcolor="35"
103                             ;;
104                         "cyan")
105                             _labelcolor="36"
106                             ;;
107                         "white")
108                             _labelcolor="37"
109                             ;;
110                         *)
111                             return 1
112                             ;;
113                     esac
114                     ;;
115                 s)
116                     _label_space="${OPTARG}"
117                     ;;
118                 x)
119                     _bash_debug=true
120                     set -xv
121                     ;;
122                 h)
123                     _help
124                     shift 1
125                     exit 0
126                     ;;
127                 -)
128                     case "${OPTARG}" in
129                         "nocolor")
130                             _nocolor=true
131                             ;;
132                         "bash-debug")
133                             _bash_debug=true
134                             set -xv
135                             ;;
136                         "help") 
137                             _help
138                             exit 0
139                             ;;
140                         "nolabel")
141                             _nolabel=true
142                             ;;
143                         "noappname")
144                             _noappname=true
145                             ;;
146                         "noadjust")
147                             _noadjust=true 
148                             ;;
149                         *)
150                             _help
151                             exit 1
152                             ;;
153                     esac
154         esac
155     done
156
157     shift $((OPTIND - 1))
158
159     # Color echo
160     #
161     # Text Color
162     # 30 => Black
163     # 31 => Red
164     # 32 => Green
165     # 33 => Yellow
166     # 34 => Blue
167     # 35 => Magenta
168     # 36 => Cyan
169     # 37 => White
170     #
171     # Background color
172     # 40 => Black
173     # 41 => Red
174     # 42 => Green
175     # 43 => Yellow
176     # 44 => Blue
177     # 45 => Magenta
178     # 46 => Cyan
179     # 47 => White
180     #
181     # Text decoration
182     # You can specify multiple decorations with ;.
183     # 0 => All attributs off (ノーマル)
184     # 1 => Bold on (太字)
185     # 4 => Underscore (下線)
186     # 5 => Blink on (点滅)
187     # 7 => Reverse video on (色反転)
188     # 8 => Concealed on
189
190     case ${1} in
191         "info")
192             _msg_type="type"
193             _output="stdout"
194             [[ "${_customized_label_color}" = false ]] && _labelcolor="32"
195             [[ "${_customized_label}"       = false ]] && _msg_label="Info"
196             shift 1
197             ;;
198         "warn")
199             _msg_type="warn"
200             _output="stdout"
201             [[ "${_customized_label_color}" = false ]] && _labelcolor="33"
202             [[ "${_customized_label}"       = false ]] && _msg_label="Warning"
203             shift 1
204             ;;
205         "debug")
206             _msg_type="debug"
207             _output="stdout"
208             [[ "${_customized_label_color}" = false ]] && _labelcolor="35"
209             [[ "${_customized_label}"       = false ]] && _msg_label="Debug"
210             shift 1
211             ;;
212         "error")
213             _msg_type="error"
214             _output="stderr"
215             [[ "${_customized_label_color}" = false ]] && _labelcolor="31"
216             [[ "${_customized_label}"       = false ]] && _msg_label="Error"
217             shift 1
218             ;;
219         "")
220             echo "Please specify the message type" >&2
221             exit 1
222             ;;
223         *)
224             echo "Unknown message type" >&2
225             exit 1
226             ;;
227     esac
228
229     _word_count="${#_msg_label}"
230     _message="${@}"
231
232     echo_type() {
233         local __time
234         if [[ "${_nolabel}" = false ]]; then
235             if [[ "${_noadjust}" = false ]]; then
236                 for __time in $( seq 1 $(( ${_label_space} - ${_word_count})) ); do
237                     echo -ne "${_adjust_chr}"
238                 done
239             fi
240             if [[ "${_nocolor}" = false ]]; then
241                 echo -ne "\e[$([[ -v _backcolor ]] && echo -n "${_backcolor}"; [[ -v _labelcolor ]] && echo -n ";${_labelcolor}"; [[ -v _decotypes ]] && echo -n ";${_decotypes}")m${_msg_label}\e[m "
242             else
243                 echo -ne "${_msg_label} "
244             fi
245         fi
246     }
247
248     echo_appname() {
249         if [[ "${_noappname}" = false ]]; then
250             if [[ "${_nocolor}" = false ]]; then
251                 echo -ne "\e[36m[${_appname}]\e[m "
252             else
253                 echo -ne "[${_appname}] "
254             fi
255         fi
256     }
257
258     for _count in $(seq "1" "$(echo -ne "${_message}\n" | wc -l)"); do
259         _echo_message=$(echo -ne "${_message}\n" |head -n "${_count}" | tail -n 1 )
260         _full_message="$(echo_appname)$(echo_type)${_echo_message}"
261         case "${_output}" in
262             "stdout")
263                 echo ${_echo_opts} "${_full_message}" >&1
264                 ;;
265             "stderr")
266                 echo ${_echo_opts} "${_full_message}" >&2
267                 ;;
268             *)
269                 echo ${_echo_opts} "${_full_message}" > ${_output}
270                 ;;
271         esac
272     done
273 }
274
275 # Show an INFO message
276 # $1: message string
277 msg_info() {
278     local _msg_opts="-a WFA"
279     if [[ "${1}" = "-n" ]]; then
280         _msg_opts="${_msg_opts} -o -n"
281         shift 1
282     fi
283     [[ "${msgdebug}" = true ]] && _msg_opts="${_msg_opts} -x"
284     [[ "${nocolor}"  = true ]] && _msg_opts="${_msg_opts} -n"
285     msg ${_msg_opts} info "${1}"
286 }
287
288 # Show an Warning message
289 # $1: message string
290 msg_warn() {
291     local _msg_opts="-a WFA"
292     if [[ "${1}" = "-n" ]]; then
293         _msg_opts="${_msg_opts} -o -n"
294         shift 1
295     fi
296     [[ "${msgdebug}" = true ]] && _msg_opts="${_msg_opts} -x"
297     [[ "${nocolor}"  = true ]] && _msg_opts="${_msg_opts} -n"
298     msg ${_msg_opts} warn "${1}"
299 }
300
301 # Show an debug message
302 # $1: message string
303 msg_debug() {
304     if [[ "${debug}" = true ]]; then
305         local _msg_opts="-a WFA"
306         if [[ "${1}" = "-n" ]]; then
307             _msg_opts="${_msg_opts} -o -n"
308             shift 1
309         fi
310         [[ "${msgdebug}" = true ]] && _msg_opts="${_msg_opts} -x"
311         [[ "${nocolor}"  = true ]] && _msg_opts="${_msg_opts} -n"
312         msg ${_msg_opts} debug "${1}"
313     fi
314 }
315
316 # Show an ERROR message then exit with status
317 # $1: message string
318 # $2: exit code number (with 0 does not exit)
319 msg_error() {
320     local _msg_opts="-a WFA"
321     if [[ "${1}" = "-n" ]]; then
322         _msg_opts="${_msg_opts} -o -n"
323         shift 1
324     fi
325     [[ "${msgdebug}" = true ]] && _msg_opts="${_msg_opts} -x"
326     [[ "${nocolor}"  = true ]] && _msg_opts="${_msg_opts} -n"
327     msg ${_msg_opts} error "${1}"
328     if [[ -n "${2:-}" ]]; then
329         exit ${2}
330     fi
331 }
332
333 # rm helper
334 # Delete the file if it exists.
335 # For directories, rm -rf is used.
336 # If the file does not exist, skip it.
337 # remove <file> <file> ...
338 remove() {
339     local _list=($(echo "$@")) _file
340     for _file in "${_list[@]}"; do
341         msg_debug "Removing ${_file}"
342         if [[ -f "${_file}" ]]; then    
343             rm -f "${_file}"
344         elif [[ -d "${_file}" ]]; then
345             rm -rf "${_file}"
346         fi
347     done
348 }
349
350 usage (){
351     local _pacman_help=false
352
353     local _wfa_usage
354     _wfa_usage() {
355         echo "Usage:"
356         echo "wfa"
357         echo "wfa <operation> [...]"
358         echo
359         echo "operations:"
360         echo "wfa {-h --help}"
361         echo "wfa {-V --version}"
362         #echo "wfa {-D --database}    <options> <package(s)>"
363         #echo "wfa {-F --files}       [options] [package(s)]"
364         echo "wfa {-Q --query}       [options] [package(s)]"
365         echo "wfa {-R --remove}      [options] <package(s)>"
366         echo "wfa {-S --sync}        [options] [package(s)]"
367         #echo "wfa {-T --deptest}     [options] [package(s)]"
368         #echo "wfa {-U --upgrade}     [options] <file(s)>"
369     }
370
371     local _wfa_usage_sync
372     _wfa_usage_sync() {
373         echo "usage:  wfa {-S --sync} [options] [package(s)]"
374         echo "options:"
375         echo "  -b, --dbpath <path>  set an alternate database location"
376         echo "      --config <path>  set an alternate configuration file"
377         echo "      --noconfirm      do not ask for any confirmation"
378
379     }
380
381     if [[ "${operation}" = "none" ]]; then
382         _wfa_usage
383     elif [[ "${_pacman_help}" = true ]]; then
384         pacman -h --${operation}
385     elif [[ "$(type -t "_wfa_usage_${operation}" )" = "function" ]]; then
386         _wfa_usage_${operation}
387     else
388         msg_error "Undefined operation." 1
389     fi
390 }
391
392 set_operation() {
393     if [[ "${operation}" = "none" ]]; then
394         operation="${1}"
395         add_pacman_args "--${operation}"
396     else
397         msg_error "only one operation may be used at a time" 1
398     fi
399 }
400
401 run_sudo() {
402     if (( ${UID} == 0 )); then
403         ${@}
404     else
405         sudo ${@}
406     fi
407 }
408
409 run_pacman() {
410     run_sudo pacman ${pacman_args} ${@}
411 }
412
413 # pacmanの引数を追加する
414 # https://github.com/FascodeNet/aptpac/blob/master/aptpac のADD_OPTION関数を参考
415 add_pacman_args() {
416     local _pacman_args_array=(${pacman_args})
417     _pacman_args_array+=(${@})
418     pacman_args=${_pacman_args_array[@]}
419     msg_debug "PACMAN ARGS: ${pacman_args}"
420 }
421
422 # 引数で指定されたパッケージがAUR以外の場所に存在しない場合にのみ正常終了します(AURのパッケージの場合に正常終了)
423 check_aur_package() {
424     local _package="${1}"
425     # 参考: https://qiita.com/Hayao0819/items/a8740a17301fafa2fdab
426     if [[ -z "$(pacman -Fq "${_package}" 2>/dev/null | grep -o ".*${_package}$")" ]]; then
427         #AUR以外のリポジトリに存在しない
428         return 0
429     else
430         return 1
431     fi
432 }
433
434
435 get_cache_dir() {
436     local _user_config_dir
437     if [[ -v XDG_CONFIG_HOME ]]; then
438         _user_config_dir="${XDG_CONFIG_HOME}"
439     else
440         _use_config_dir="${HOME}/.config"
441     fi
442     if [[ -f "${_use_config_dir}/user-dirs.dirs" ]]; then
443         source "${_use_config_dir}/user-dirs.dirs"
444     fi
445     if [[ -v XDG_CACHE_HOME ]]; then
446         echo -n "${XDG_CACHE_HOME}"
447         return 0
448     else
449         echo -n "${HOME}/.cache"
450         return 0
451     fi
452 }
453
454 # Usage: get_srcinfo_data <path> <var>
455 : << "DISABLED"
456 get_srcinfo_data() {
457     local _srcinfo="${1}" _ver="${2}"
458     local _srcinfo_json=$(python << EOF
459 from srcinfo.parse import parse_srcinfo; import json
460 text = """
461 $(cat ${1})
462 """
463 parsed, errors = parse_srcinfo(text)
464 print(json.dumps(parsed))
465 EOF
466     )
467     echo "${_srcinfo_json}" | jq -r .${2}
468 }
469 DISABLED
470
471 get_srcinfo_data() {
472     local _srcinfo="${1}"
473     local _var="${2}"
474     local _pkg _output
475     if [[ ! -f "${_srcinfo}" ]]; then
476         msg_error ".SRCINFO does not exist."
477     fi
478     for _pkg in $(cat "${_srcinfo}" | grep "${_var} = " | cut -d ' ' -f "3"); do
479         _output+=(${_pkg})
480     done
481     echo -n "${_output[@]}"
482 }
483
484
485 # AURからパッケージをビルドしてインストールします
486 # 現在1つのパッケージしか指定できません
487 install_aur_package() {
488     local _package="${1}"
489
490     # Create cache dir
491     if [[ ! -v wfa_cache_dir ]]; then
492         wfa_cache_dir="$(get_cache_dir)/wfa"
493     fi
494     mkdir -p "${wfa_cache_dir}/archive"
495     mkdir -p "${wfa_cache_dir}/build/${_package}"
496
497     # AurJsonから値を取得
498     local _aur_json=$(curl -sL "https://aur.archlinux.org/rpc/?v=5&type=search&by=name&arg=${_package}" | jq -r)
499     if (( "$(echo "${_aur_json}" | jq -r ".resultcount")" == 0 )); then
500         msg_error "Could not find all required packages:\n      ${_package}" 1
501     fi
502
503     local _found_packages="$(echo "${_aur_json}" | jq -r --tab '.results[].Name')"
504     #msg_debug "Found package: $(echo ${_found_packages} | tr '\n' ' ')"
505
506     if [[ -n "$(echo "${_found_packages}" | grep -x "${_package}" )" ]]; then
507         msg_debug "Select a package ${_package} with an exact name match"
508     else
509         msg_error "No package with an exact name match was found." 1
510     fi
511
512     # PKGBUILDをダウンロード
513     msg_info "Download PKGBUILD of ${_package}"
514     _aur_json=$(echo "${_aur_json}" | jq -r ".results[] | select(.Name == \"${_package}\")" )
515     local _aur_snapshot_url="${aururl%/}$(echo "${_aur_json}" | jq -r ".URLPath")"
516     local _aur_version="$(echo "${_aur_json}" | jq -r ".Version")"
517     msg_debug "Get PKGBUILD from ${_aur_snapshot_url}"
518
519     local _pkgbuild_archive_path="${wfa_cache_dir}/archive/${_package}-${_aur_version}"
520     local _download_pkgbuild=true
521     if [[ -f "${_pkgbuild_archive_path}" ]]; then
522         msg_warn "PKGBUILD has already been downloaded."
523         msg_warn -n "Do you want to overwrite and download? [n] :"
524         local _yes_or_no
525         if [[ "${noconfirm}" = true ]]; then
526             echo
527             _yes_or_no="No"
528         else
529             read _yes_or_no
530         fi
531         case "${_yes_or_no}" in
532             "y" | "Y" | "yes" | "Yes" | "YES" ) _download_pkgbuild=true  ;;
533             *                                 ) _download_pkgbuild=false ;;
534         esac
535     fi
536     if [[ "${_download_pkgbuild}" = true ]]; then
537         remove "${_pkgbuild_archive_path}"
538         curl -L -C - -f -o "${_pkgbuild_archive_path}" "${_aur_snapshot_url}"
539     fi
540
541     # PKGBUILDを展開
542     msg_info "Unpacking the tarball of PKGBUILD ..."
543     tar -xv -f "${_pkgbuild_archive_path}" -C "${wfa_cache_dir}/build/" 2>/dev/null 1>&2
544
545     # .SRCINFOを解析
546     local _build_dir="${wfa_cache_dir}/build/${_package}"
547     if [[ ! -f "${_build_dir}/.SRCINFO" ]]; then
548         msg_warn ".SRCINFO was not found.\nGenerate it using makepkg."
549         makepkg -p "${_build_dir}/PKGBUILD" --printsrcinfo > "${_build_dir}/.SRCINFO"
550     fi
551
552     local _makedepends="$(get_srcinfo_data "${_build_dir}/.SRCINFO" "makedepends")"
553     local _depends="$(get_srcinfo_data "${_build_dir}/.SRCINFO" "depends")"
554     local _conflicts="$(get_srcinfo_data "${_build_dir}/.SRCINFO" "conflicts")"
555     msg_debug "makedepends: ${_makedepends}"
556     msg_debug "depends: ${_depends}"
557     msg_debug "conflicts: ${_conflicts}"
558
559
560     # 衝突を確認
561     local _pkg _conflicts_found=false
562     for _pkg in ${_conflicts[@]}; do
563         if pacman -Qq "${_pkg}" 2>/dev/null 1>&2 ; then
564             _conflicts_found=true
565             msg_error "${_package} is colliding with ${_pkg}"
566         fi
567     done
568     if [[ ! "$(pacman -Qq "${_package}")" = "${_package}" ]]; then
569         msg_error "${_package} is colliding with $(pacman -Qq "${_package}")"
570         _conflicts_found=true
571     fi
572     if [[ "${_conflicts_found}" = true ]]; then
573         msg_error "A conflict was found." 1
574     fi
575
576
577     # 依存パッケージをインストール
578     msg_info "Install dependent packages..."
579     local _force_aur="${force_aur}"
580     force_aur=false
581     install_package "${_makedepends}" "${_depends}"
582     force_aur="${_force_aur}"
583     unset _force_aur
584
585
586 }
587
588 # バージョンを表示して終了
589 operation_version() {
590     # Pyalpmからlibalpmの値を取得
591     # 参考: https://pyalpm.readthedocs.io/en/latest/pyalpm/pyalpm.html
592     local _libalpm_version="$(python3 -c 'import pyalpm; print(pyalpm.alpmversion())')"
593     local _pacman_version="$(pacman -Q pacman | cut -d ' ' -f 2)"
594     echo "wfa v${wfa_version} - pacman v${_pacman_version} - libalpm v${_libalpm_version}"
595 }
596
597 operation_remove() {
598     run_pacman "${specified_packages[@]}"
599 }
600
601 # Usage: install_package <package1> <package2>...
602 install_package() {
603     local _package
604     for _package in ${@}; do
605         if ! check_aur_package "${_package}" && [[ "${force_aur}" = false ]]; then
606             # 公式パッケージなのでpacmanでそのままインストール
607             run_pacman "${_package}"
608         else
609             # AUR上のパッケージの場合の処理
610             install_aur_package "${_package}"
611             #msg_error "Getting the AUR package has not been implemented yet." 1
612         fi
613     done
614 }
615
616 operation_sync(){
617     install_package "${specified_packages[@]}"
618 }
619
620
621 # Parse options
622 ARGUMENT="${@}"
623 _opt_short="QRShVdb:a"
624 _opt_long="query,remove,sync,help,version,debug,dbpath:,aururl,aur,noconfirm,config:"
625
626 OPT=$(getopt -o ${_opt_short} -l ${_opt_long} -- ${ARGUMENT})
627 [[ ${?} != 0 ]] && exit 1
628 unset _opt_short _opt_long
629
630 eval set -- "${OPT}"
631 msg_debug "Argument: ${OPT}"
632
633 while :; do
634     case ${1} in
635         -Q | --query)
636             set_operation "query"
637             shift 1
638             ;;
639         -R | --remove)
640             set_operation "remove"
641             shift 1
642             ;;
643         -S | --sync)
644             set_operation "sync"
645             shift 1
646             ;;
647         -V | --version)
648             set_operation "version"
649             shift 1
650             ;;
651         --)
652             shift
653             break
654             ;;
655         *)
656             shift 1
657             ;;
658     esac
659 done
660
661 eval set -- "${OPT}"
662
663 while :; do
664     case ${1} in
665         -a | --aur)
666             force_aur=true
667             msg_debug "Assume targets are from the AUR"
668             shift 1
669             ;;
670         -d | --debug)
671             debug=true
672             add_pacman_args "--debug"
673             shift 1
674             ;;
675         -b | --dbpath)
676             add_pacman_args "--dbpath '${2}'"
677             shift 2
678             ;;
679         --aururl)
680             aururl="${2}"
681             shift 2
682             ;;
683         --noconfirm)
684             add_pacman_args "--noconfirm"
685             noconfirm=true
686             shift 1
687             ;;
688         --config)
689             pacman_config="${2}"
690             add_pacman_args "--config \"${2}\""
691             shift 2
692             ;;
693         -h | --help)
694             usage
695             shift 1
696             exit 0
697             ;;
698         --)
699             shift
700             break
701             ;;
702         *)
703             shift 1
704             ;;
705     esac
706 done
707
708 specified_packages=(${@})
709
710 case "${operation}" in
711     "version")
712         operation_version
713         ;;
714     "sync")
715         operation_sync
716         ;;
717     "remove")
718         operation_remove
719         ;;
720     "none")
721         exit 0
722         ;;
723     *)
724         msg_error "Undefined operation." 1
725         ;;
726 esac