qi: Added new (implicit) special variable 'opt_skiprecipe' to ignore a recipe
[dragora.git] / qi / src / qi.in
blobf1002adaa466e0fb36f2ab054b7048051a28f501
1 #! /bin/sh -
2 # Copyright (C) 2016-2022 Matias Fonzo <selk@dragora.org>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # EXIT STATUS
18 # 0 = Successful completion
19 # 1 = Minor common errors (e.g: help usage, support not available)
20 # 2 = Command execution error
21 # 3 = Integrity check error for compressed files
22 # 4 = File empty, not regular, or expected
23 # 5 = Empty or not defined variable
24 # 6 = Package already installed
25 # 10 = Network manager error
27 PROGRAM="${0##*/}"
29 LC_ALL=C; # Override locale settings.
30 umask 022; # Remove write permission for group and other.
31 export LC_ALL
33 ### Functions
35 usage()
37 printf '%s' \
38 "Usage: $PROGRAM COMMAND [OPTIONS] [FILE]...
39 "'A simple but well-integrated package manager.
41 List of commands:
42 warn Warn about files that will be installed
43 install Install packages
44 remove Remove packages
45 upgrade Upgrade packages
46 extract Extract packages for debugging purposes
47 create Create a .tlz package from directory
48 build Build packages using recipe names
49 order Resolve build order through .order files
51 Options when installing, removing, or upgrading software packages:
52 -f, --force Force upgrade of pre-existing packages
53 -k, --keep Keep package directory when remove/upgrade
54 -p, --prune Prune conflicts
55 -P, --packagedir=<dir> Set directory for package installations
56 -t, --targetdir=<dir> Set target directory for symbolic links
57 -r, --rootdir=<dir> Use the fully qualified named directory as
58 the root directory for all qi operations
59 Note: the target directory and the package
60 directory will be relative to the specified
61 directory, excepting the graft log file
63 Options when building software packages using recipes:
64 -a, --architecture Set architecture name for the package
65 -j, --jobs Parallel jobs for the compiler
66 -k, --keep Keep ${srcdir} or ${destdir} when build
67 -S, --skip-questions Skip questions on completed recipes
68 -1, --increment Increment release number (${release} + 1)
69 -n, --no-package Do not create a .tlz package
70 -i, --install Install package after the build
71 -u, --upgrade Upgrade package after the build
72 -o, --outdir=<dir> Where the packages produced will be written
73 -w, --worktree=<dir> Where archives, patches, recipes are expected
74 -s, --sourcedir=<dir> Where compressed sources will be found
76 Other options:
77 -N, --no-rc Do not read the configuration file
78 -v, --verbose Be verbose (an extra -v gives more)
79 -L, --show-location Print default directory locations and exit
80 -h, --help Display this help and exit
81 -V, --version Output version information and exit
83 Some influential environment variables:
84 TMPDIR Temporary directory for sources
85 QICFLAGS C compiler flags (to be used on CFLAGS)
86 QICXXFLAGS C++ compiler flags (to be used on CXXFLAGS)
87 QILDFLAGS Flags for the linker (to be used on LDFLAGS)
88 QICPPFLAGS C/C++ preprocessor flags (to be used on CPPFLAGS)
89 SOURCE_DATE_EPOCH Last modification time for created packages
91 When FILE is -, read standard input.
93 Exit status: 0 for a normal exit, 1 for minor common errors (help usage,
94 support not available, etc), 2 to indicate a command execution error;
95 3 for integrity check error on compressed files, 4 for empty, not
96 regular, or expected files, 5 for empty or not defined variables,
97 6 when a package already exist, 10 for network manager errors.
99 Qi home page: https://www.dragora.org
103 warn()
105 printf '%s\n' "$@" 1>&2
108 is_readable()
110 if test -e "$1"
111 then
112 if ! test -r "$1"
113 then
114 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
115 return 1
117 else
118 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
119 return 1
123 # Portable alternative to the file operator -nt (among shells)
124 is_newer()
126 if test -n "$(find "$1" -prune -newer "$2" -print)"
127 then
128 return 0
130 return 1
133 # Determine whether $2 matches pattern $1
134 fnmatch()
136 case $2 in
138 return 0
141 return 1
143 esac
146 chkstatus_or_exit()
148 status=$?
150 if test $status -ne 0
151 then
152 echo "^ Return status = $status" 1>&2
153 exit "${1-2}"; # If not given, defaults to 2
156 unset -v status
159 readconfig()
161 if test "${_readconfig:-readconfig}" = readconfig
162 then
163 if is_readable "$HOME/.qirc" 2> /dev/null
164 then
165 _rcfile="$HOME/.qirc"
168 echo "Importing configuration file from \`${_rcfile}' ..."
169 . "$_rcfile" || chkstatus_or_exit 5
171 unset -v _readconfig
174 ### Mode functions
176 mode_build()
178 recipe=$1
180 echo ""
181 echo "{@} Building \"${recipe}\" ..."
183 # A recipe is any valid regular file. Qi sets priorities for reading
184 # a recipe, the order in which qi looks for a recipe is:
186 # 1. Current working directory.
188 # 2. If the specified path name does not contain "recipe" as the last
189 # component. Qi will complete it by adding "recipe" to the path
190 # name.
192 # 3. If the recipe is not in the current working directory, it will be
193 # searched under '${worktree}/recipes'. The last component will be
194 # completed adding "recipe" to the specified path name.
196 if test ! -f "$recipe"
197 then
198 if test -f "${recipe}/recipe"
199 then
200 recipe="${recipe}/recipe"
201 elif test -f "${worktree}/recipes/${recipe}/recipe"
202 then
203 recipe="${worktree}/recipes/${recipe}/recipe"
207 test -f "$recipe" || {
208 warn "\`${recipe}' is not a regular file."
209 exit 4
212 # Complain if the file name is not "recipe"
214 if test "${recipe##*/}" != recipe
215 then
216 warn "\`${recipe}' is not a valid recipe name."
217 exit 4
220 # Start preparations to import the recipe
222 # Get working directory and base name of the recipe
224 CWD="$(CDPATH='' cd -P -- "$(dirname -- "$recipe")" && pwd -P)"
225 recipe="$(basename -- "$recipe")"
227 # Check readability for load the recipe on success
229 is_readable "${CWD}/$recipe" || exit 4
231 # Create external directories if needed
232 for directory in "${worktree}/archive" "${worktree}/patches" "${worktree}/recipes" "$tardir"
234 test -d "$directory" || mkdir -p -- "$directory"
235 done
236 unset -v directory
238 # Variables treatment for the current and the next recipe.
240 # Unset special variables that can only be predefined in
241 # the recipe and does not come from '${sysconfdir}/qirc'
243 unset -v \
244 srcdir destdir pkgname pkgversion pkgcategory program version release \
245 fetch description homepage license replace full_pkgname docs docsdir \
246 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
248 # The following variables must be restored, later
249 save_arch="${save_arch:=$arch}"
250 save_jobs="${save_jobs:=$jobs}"
251 save_outdir="${save_outdir:=$outdir}"
252 save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
253 save_tarlz_compression_options="${save_tarlz_compression_options:=$tarlz_compression_options}"
255 # Reset variable values in case of return
256 arch="$save_arch"
257 jobs="$save_jobs"
258 outdir="$save_outdir"
259 opt_nopkg="$save_opt_nopkg"
260 tarlz_compression_options="$save_tarlz_compression_options"
262 # The following variables cannot be redefined on a recipe
263 readonly worktree netget rsync
265 # Import the recipe
267 . "${CWD}/$recipe"
268 chkstatus_or_exit
270 # Check if 'opt_skiprecipe' has been declared to ignore
271 # the current recipe and continue with the next recipe
272 if test "$opt_skiprecipe" = "opt_skiprecipe"
273 then
274 unset -v opt_skiprecipe
275 warn "${recipe}: The variable 'opt_skiprecipe' has been used here."
276 return 0;
279 # Check for required variables
280 if test -z "$program"
281 then
282 warn "${recipe}: The variable 'program' is not defined."
283 exit 5
285 if test -z "$version"
286 then
287 warn "${recipe}: The variable 'version' is not defined."
288 exit 5
290 if test -z "$arch"
291 then
292 warn "${recipe}: The variable 'arch' is not defined."
293 exit 5
295 if test -z "$release"
296 then
297 warn "${recipe}: The variable 'release' is not defined."
298 exit 5
301 # Pre-settings before to start building
303 # Increment the release number if the option was given
304 if test "$opt_incr_release" = opt_incr_release
305 then
306 release=$(( release + 1 ))
309 # Allow the dot as definition for 'tardir'
310 if test "$tardir" = .
311 then
312 tardir="$CWD"
315 # Set default values for the following special variables
317 pkgname="${pkgname:=$program}"
318 pkgversion="${pkgversion:=$version}"
319 srcdir="${srcdir:=${program}-$version}"
320 destdir="${destdir:=${TMPDIR}/package-$pkgname}"
322 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
323 if test -n "$pkgcategory"
324 then
325 pkgcategory="@${pkgcategory}"
328 # Compose the full package name
329 full_pkgname="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
331 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
332 outdir="${outdir}/${arch}"
334 # If a package is going to be created the existence of a
335 # previous build will be detected and reported. Under normal
336 # conditions the recipe is built as long as it is newer than
337 # the produced package, if not, we warn to the user about it.
338 # Rebuilding the package is possible (through the force ;-)
340 if test "$opt_nopkg" != opt_nopkg && \
341 { test "$opt_force" != opt_force && \
342 test -e "${outdir}/${full_pkgname}.tlz" ; }
343 then
344 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
345 then
346 warn \
347 "" \
348 "This recipe is more RECENT than the produced package:" \
349 "" \
350 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
351 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
352 "" \
353 " This recipe will be processed ..." \
355 elif test -e "${CWD}/post-install" && \
356 is_newer "${CWD}/post-install" "${CWD}/$recipe"
357 then
358 warn \
359 "" \
360 "The post-install script is more RECENT than the recipe:" \
361 "" \
362 "$( stat -c "%y %n" "${CWD}/post-install" )" \
363 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
364 "" \
365 " This recipe will be re-processed ..." \
367 touch "${CWD}/$recipe"
368 elif test "$opt_skipqsts" = opt_skipqsts
369 then
370 warn "Recipe for '${full_pkgname}.tlz': [Ignored]." ""
371 return 0
372 else
373 warn \
374 "" \
375 "This recipe ALREADY produced a package:" \
376 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
377 "" \
378 "The recipe is still OLDER than the produced package:" \
379 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
380 "" \
381 " Probably nothing has changed." \
384 # In non-interactive mode, the user is asked about
385 # rebuilding the package. In interactive mode,
386 # the user need to pass the option explicitly
387 if test ! -t 0
388 then
389 printf '%s\n' \
390 "Do you want to rebuild this package?" \
391 "1) Yes, built it" \
392 "2) No, skip it [default]" \
393 "3) Resume, skipping completed recipes" \
394 "Enter an option number:" > /dev/tty
395 IFS= read -r ANSWER < /dev/tty || exit 2;
396 case $ANSWER in
398 echo "$ANSWER" > /dev/tty
399 unset -v ANSWER
402 unset -v ANSWER
403 echo "=== Building unprocessed or recently modified recipe(s) ..." > /dev/tty
404 echo ""
405 opt_skipqsts=opt_skipqsts
406 readonly opt_skipqsts
407 return 0
410 unset -v ANSWER
411 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
412 echo ""
413 return 0
415 esac
416 else
417 warn "Use the --force option to reprocess ${CWD}/${recipe}." ""
418 return 6;
423 # Fetch remote sources
425 echo "=== Fetching remote sources if needed ..."
426 if test -n "$fetch"
427 then
428 for origin in $fetch
430 echo "=== Looking for $origin ..."
432 _source="${origin##*/}"; # Get the file name.
434 echo "=== Verifying checksum file found \`${tardir}/${_source}.sha256'"
435 if test -e "${tardir}/${_source}.sha256"
436 then
437 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
438 chkstatus_or_exit
439 continue;
440 else
441 warn " Checksum file \`${_source}.sha256' does not exist"
444 # Download source or resume if allowed
446 if test ! -e "${tardir}/$_source"
447 then
448 warn "=== Attempting to get it from $origin ..."
451 case $origin in
452 rsync://*)
454 cd -- "$tardir" && $rsync "$origin" || exit $?
455 sha256sum "$_source" > "${_source}.sha256"
456 ); chkstatus_or_exit 10
458 *://*)
460 cd -- "$tardir" && $netget "$origin" || exit $?
461 sha256sum "$_source" > "${_source}.sha256"
462 ); chkstatus_or_exit 10
465 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
466 exit 4
467 esac
468 done
469 unset -v origin _source
470 else
471 warn "${recipe}: The variable 'fetch' is empty."
474 # Prepare special directories for build the source,
475 # the destination and the output of the package
477 echo "=== Preparing directories ..."
479 if test -z "$keep_srcdir"
480 then
481 if test -e "${TMPDIR}/$srcdir"
482 then
483 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
484 echo "removed directory: '${TMPDIR}/$srcdir'"
486 else
487 warn \
488 "WARNING: The variable 'keep_srcdir' has been set (${keep_srcdir})."
491 if test -z "$keep_destdir"
492 then
493 if test -e "$destdir"
494 then
495 rm -r -- "$destdir" || chkstatus_or_exit
496 echo "removed directory: '$destdir'"
498 mkdir -p -- "$destdir" || chkstatus_or_exit
499 echo "mkdir: created directory '$destdir'"
502 if test ! -e "$outdir"
503 then
504 mkdir -p -- "$outdir" || chkstatus_or_exit
505 echo "mkdir: created directory '$outdir'"
508 echo "=== Changing to '${TMPDIR}' ..."
509 cd -- "$TMPDIR" || chkstatus_or_exit
511 # Set trap before to run the build() function in order
512 # to catch the return status, exit code 2 if fails
514 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
516 # Determine if the debugging indicators of the shell should be
517 # retained, assuming that it has been previously passed
518 case $- in *x*)
519 _xtrace_flag=_xtrace_flag_is_set ;;
520 esac
522 echo "=== Running the 'build' function ..."
523 build
524 unset -f build
526 # Check recipe to run (extra) defined functions by the packager
527 for _definition in \
528 $(awk '!/^build[ (]/ && /^[^ {}]+ *\(\)/{ gsub(/[()]/, "", $1); print $1 }' "${CWD}/$recipe")
530 # Check if it is a shell function
531 case $(LC_ALL=C type $_definition) in
532 *function*)
533 # Call and undo the function after executing it
534 $_definition
535 unset -f $_definition
537 esac
538 done
539 unset -v _definition
541 # Turn off possible shell flags coming from the recipe
543 set +e
544 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
545 then
546 set +x
549 # Reset given signals
550 trap - EXIT HUP INT QUIT ABRT TERM
552 # If 'destdir' is empty, the package won't be created
553 if rmdir -- "$destdir" 2> /dev/null
554 then
555 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
556 opt_nopkg=opt_nopkg
559 # Create (make) the package
561 if test "$opt_nopkg" != opt_nopkg
562 then
563 # Edit the recipe when 'release' is incremented
564 if test "$opt_incr_release" = opt_incr_release
565 then
566 echo ",s/^\\(release\\)=.*/\\1=${release}/"$'\nw' | \
567 ed "${CWD}/$recipe" || chkstatus_or_exit
570 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
572 # Include a copy of the recipe into the package
573 cp -p "${CWD}/$recipe" \
574 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
575 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
576 chkstatus_or_exit
578 # Produce a checksum file for recipe copy
579 cd -- "${destdir}/var/lib/qi" && \
580 sha256sum "${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
581 ); chkstatus_or_exit 4
583 # Detect post-install script for inclusion
585 if test -f "${CWD}/post-install"
586 then
587 echo "${CWD}/post-install: Detected."
588 cp -p "${CWD}/post-install" \
589 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
590 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
591 chkstatus_or_exit
594 # Detect declared package name(s) for later replacement
596 if test -n "$replace"
597 then
598 warn \
599 "=== The following package names has been declared for replacement:" \
600 "$replace"
602 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
603 for item in $replace
605 printf '%s\n' "$replace" >> \
606 "${destdir}/var/lib/qi/${full_pkgname}.replace"
607 done
608 unset -v item
611 # Create (external) meta file for package information,
612 # make a copy of it for the package database
613 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
614 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
615 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
616 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
618 # Produce the package
619 cd -- "$destdir" && mode_create "${outdir}/${full_pkgname}.tlz"
622 # Back to the current working directory
623 cd -- "$CWD" || chkstatus_or_exit
625 echo "=== Deleting 'srcdir' or 'destdir' ..."
626 if test "$opt_keep" != opt_keep
627 then
628 if test -z "$keep_srcdir"
629 then
630 srcdir="${srcdir%%/*}"; # Directory name without parents.
631 if test -e "${TMPDIR}/$srcdir"
632 then
633 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
634 echo "removed directory: '${TMPDIR}/$srcdir'"
637 if test -z "$keep_destdir"
638 then
639 if test -e "$destdir"
640 then
641 rm -rf -- "$destdir" || chkstatus_or_exit
642 echo "removed directory: '$destdir'"
645 else
646 warn \
647 " The following directories will be preserved:" \
648 "${TMPDIR}/$srcdir" \
649 "$destdir" \
650 "" \
651 "The '--keep' option has been used."
654 # Install or upgrade the package after build
655 if test "$opt_nopkg" != opt_nopkg
656 then
657 if test "$opt_install" = opt_install
658 then
659 mode_install "${outdir}/${full_pkgname}.tlz"
660 elif test "$opt_upgrade" = opt_upgrade
661 then
662 mode_upgrade "${outdir}/${full_pkgname}.tlz"
666 warn "{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
669 mode_create()
671 directory="$(dirname -- "$1")"
673 # Perform sanity checks
675 if ! fnmatch '/?*' "$directory"
676 then
677 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
678 exit 4
680 is_readable "$directory" || exit 4
682 name="$(basename -- "$1")"
684 echo ""
685 echo "{#} Creating package name \`${name}' ..."
687 if test "$name" = "${name%.tlz}"
688 then
689 warn "Package format '$name' not supported." \
690 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
691 exit 4
694 # If needed, assign default values for compression options
695 tarlz_compression_options="${tarlz_compression_options:=-9 --solid}"
697 # Pass extra options to tarlz(1)
698 if test -n "$SOURCE_DATE_EPOCH"
699 then
700 tarlz_compression_options="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
703 ( umask 022 ; tarlz $tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
704 chkstatus_or_exit 3
706 ( cd -- "$directory" && sha256sum "$name" > "${name}.sha256" )
707 chkstatus_or_exit 4
709 warn "{#} Package \"${name}\" created on ${directory}." ""
711 # Remove used variables
712 unset -v directory name
715 mode_remove()
717 expunge="${packagedir}/$(basename -- "$1" .tlz)"
719 echo ""
720 echo "{<} Removing \`$rootdir${expunge}' ..."
722 # Complain if the package directory cannot be well-read
724 is_readable "$rootdir${expunge}" || exit 4
726 # Validate package directory as such
728 test -d "$rootdir${expunge}" || {
729 warn "Package '$rootdir${expunge}' is not a valid directory."
730 exit 4
733 # Remove package from Graft control
735 # Scan for possible conflicts, stop if arise
736 if test "$opt_prune" != opt_prune
737 then
738 echo "=== Checking for possible conflicts ..."
739 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
740 grep "^CONFLICT"
741 then
742 warn "" \
743 " A conflict occurred during uninstallation;" \
744 "Unless the --prune option is given, this package will be PRESERVED."
745 exit 6;
749 # Remove objects (files, links or directories) from the target
750 # directory that are in conflict with the package directory
752 echo "=== Pruning any conflict ..."
753 graft -p -D -u $graft_r -t "$targetdir" "$expunge"
754 chkstatus_or_exit 2
756 echo "=== Disabling links ..."
757 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
758 chkstatus_or_exit 2
760 # Delete package directory
761 if test "$opt_keep" != opt_keep
762 then
763 echo "=== Deleting package directory ..."
764 if is_readable "${rootdir}$expunge"
765 then
766 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
767 echo "removed directory: '${rootdir}$expunge'"
771 warn "{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
772 unset -v expunge
775 mode_install()
777 # Complain if the package cannot be well-read
779 is_readable "$1" || exit 4
781 # Complain if the package does not end in .tlz
783 if ! fnmatch '*.tlz' "$1"
784 then
785 warn "\`${1}' does not end in .tlz"
786 exit 4
789 # Make preparations to install the package
791 echo ""
792 echo "{>} Installing $1 ..."
794 echo "=== Checking tarball integrity ..."
795 tarlz --missing-crc -tf "$1" > /dev/null
796 chkstatus_or_exit 3
798 # To accept random directory from the upgrade mode
799 _packagedir="$2"
800 _packagedir="${_packagedir:=$packagedir}"
802 # Create package directory using 'name'
804 name="$(basename -- "$1" .tlz)"; # Get the file name.
806 if ! test -d "$rootdir${_packagedir}/$name"
807 then
808 mkdir -p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
809 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
812 # Scan for possible conflicts, stop if arise
813 if test "$opt_prune" != opt_prune
814 then
815 echo "=== Checking for possible conflicts ..."
816 if graft -i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
817 grep "^CONFLICT"
818 then
819 warn "" \
820 " A conflict occurred during installation;" \
821 "Unless the --prune option is given, this package won't be LINKED."
822 exit 6;
826 echo "=== Decompressing package ..."
827 ( cd -- "$rootdir${_packagedir}/$name" && tarlz -xpf - ) < "$1"
828 chkstatus_or_exit 3
830 # Transite package to Graft control
832 # Remove objects (files, links or directories) from the target
833 # directory that are in conflict with the package directory
834 echo "=== Pruning any conflict ..."
835 graft -p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
836 chkstatus_or_exit 2
838 echo "=== Enabling symbolic links ..."
839 graft -i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
840 chkstatus_or_exit 2
842 # Avoid unnecessary runs coming from the upgrade mode,
843 # this is when the incoming package is **pre-installed**
845 if test "$_isUpgrade" != _isUpgrade.on
846 then
847 # Show package description
848 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
849 then
850 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
851 elif test -r "${1}.txt"
852 then
853 # From external meta file (current directory)
854 awk '/^#/' "${1}.txt"
855 else
856 warn "Description file not found for '$name'."
859 # Check and run the post-install script if exist
860 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
861 then
862 echo "=== Running post-install script for \`${name}' ..."
864 # Rely on 'targetdir' if 'rootdir' is empty
865 cd -- "${rootdir:=$targetdir}"/ && \
866 . "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
870 # Check if there are declared packages for replacement
871 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
872 then
873 while read -r line
875 for replace in "$rootdir${_packagedir}/${line%%_*}"_*
877 if ! test -e "$replace"
878 then
879 warn " Declared package \`${replace}' to be replaced does not exist. [Ignored]"
880 continue;
883 replace="${replace##*/}"
885 # The search for the package to be replaced cannot
886 # be the same to the incoming package, even to the
887 # temporary location coming from the upgrade mode
888 if test "$replace" = "$name" || \
889 test "_x_${replace}" = "_x_${PRVLOC##*/}"
890 then
891 continue;
894 warn "WARNING: Replacing package \`${replace}' ..."
896 # Since the links belongs to the new package, only
897 # those which are not in conflict can be deleted.
898 # To complete, we will remove the package directory
900 graft -d -D -u $graft_r \
901 -t "$targetdir" "$replace" > /dev/null 2>&1
903 rm -rf -- "$rootdir${_packagedir}/$replace"
904 done
905 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
909 warn "{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
910 unset -v name _packagedir; # Remove used variables.
913 mode_order()
915 # Complain if the file cannot be well-read
917 is_readable "$1" || exit 4
919 # Complain if the file does not end in .order
921 if ! fnmatch '*.order' "$1"
922 then
923 warn "\`${1}' does not end in .order"
924 exit 4
927 # Get a clean list of the file while printing its contents in
928 # reverse order. Last awk(1) in the pipeline eliminates
929 # non-consecutive lines: duplicates, blank lines, and colons.
930 # Comment lines beginning with '#' are allowed; parentheses
931 # are simply ignored
933 awk '{
934 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
935 for( i=NF; i > 0; i-- ) print $i
936 }' "$1" | awk '!s[$0]++'
939 mode_upgrade()
941 # Complain if the package does not end in .tlz
943 if ! fnmatch '*.tlz' "$1"
944 then
945 warn "\`${1}' does not end in .tlz"
946 exit 4
949 echo ""
950 echo "{^} Upgrading $1 ..."
952 incoming="$(basename -- "$1" .tlz)"; # Get the file name.
954 # Check package pre-existence
955 if test "$opt_force" != opt_force && \
956 test -e "$rootdir${packagedir}/$incoming"
957 then
958 warn "" \
959 " The package to be upgraded already exist;" \
960 "Unless the --force option is given, this package won't be UPGRADED."
961 exit 6;
964 # Ignore some signals until the upgrade process is complete
965 trap "" HUP INT QUIT ABRT TERM
967 # Check blacklisted packages before to proceed with the upgrade
969 echo "=== Checking blacklist ..."
970 for item in $blacklist
972 case $item in
973 "${incoming%@*}"*)
974 warn \
975 " A blacklisted package name has been detected:" \
976 "$incoming" \
977 "" \
978 "^ This package will be INSTALLED instead of being upgraded ..."
979 opt_prune=opt_prune mode_install "$1"
980 return 0;
982 esac
983 done
984 unset -v item
986 # Prepare the package to install it in a temporary but custom location
988 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
989 PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
991 echo "=== Pre-installing package in temporary location ..."
993 opt_prune=opt_prune; # Turns on prune operation.
995 _isUpgrade=_isUpgrade.on mode_install "$1" "$PRVLOC" > /dev/null
996 _isUpgrade=_isUpgrade.off
998 echo "=== Looking for installations under the same name ..."
999 for previous_package in "$rootdir${packagedir}/${incoming%%_*}"_*
1001 test -e "$previous_package" || continue
1003 # Previous package directory to be removed cannot be
1004 # the same to the temporary (private) location
1005 if test "$previous_package" = "$PRVLOC"
1006 then
1007 continue;
1010 # The directory of the package could be preserved if --keep
1011 mode_remove "$previous_package"
1012 done
1013 unset -v previous_package
1015 # Re-install incoming package to the final location
1017 mode_install "$1"
1018 opt_prune=opt_prune.off; # Turns off prune operation.
1020 echo "=== Deleting temporary location ..."
1021 if is_readable "$PRVLOC"
1022 then
1023 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1024 echo "removed directory: '$PRVLOC'"
1026 unset -v PRVLOC
1028 warn "{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1029 unset -v incoming
1031 # Reset given signals
1032 trap - HUP INT QUIT ABRT TERM
1035 mode_warn()
1037 # Complain if the package cannot be well-read
1039 is_readable "$1" || exit 4
1041 # Complain if the package does not end in .tlz
1043 if ! fnmatch '*.tlz' "$1"
1044 then
1045 warn "\`${1}' does not end in .tlz"
1046 exit 4
1049 # List content of files excluding directories
1050 while test -f "$1"
1052 tarlz -tvvf "$1" | awk '!/^drwx/'
1053 chkstatus_or_exit 3
1054 shift;
1055 done
1056 return 0
1059 mode_extract()
1061 # Perform sanity checks before package extraction
1063 is_readable "$1" || exit 4
1065 test -f "$1" || {
1066 warn "\`${1}' is not a regular file."
1067 exit 4
1070 # Preparations to extract the package
1072 name="$(basename -- "$1" .tlz)"
1074 # Set random directory under 'TMPDIR' using 'name'
1075 PRVDIR=$(mktemp -dp "$TMPDIR" ${name}.XXXXXXXXXXXX) || exit 2
1077 # Trap to remove 'PRVDIR' on disruptions
1078 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1080 # Create 'PRVDIR' removing access for all but user
1081 ( umask 077 ; mkdir -- "$PRVDIR" )
1082 mkdir -p -- "$PRVDIR"
1083 chmod 700 -- "$PRVDIR"
1085 echo ""
1086 echo "{-} Extracting package \`${name}' ..."
1088 ( umask 000 ; cd -- "$PRVDIR" && tarlz -xvf - ) < "$1"
1089 if test $? -ne 0
1090 then
1091 # Try to remove (empty) 'PRVDIR' on failure
1092 rmdir -- "$PRVDIR"
1093 exit 3;
1096 warn "" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1098 # Remove used variables
1099 unset -v name PRVDIR
1101 # Reset given signals
1102 trap - HUP INT ABRT TERM
1105 ### Extra functions to be used during the modes
1107 unpack()
1109 for file in "$@"
1111 case $file in
1112 *.tar)
1113 tar -tf "$file" > /dev/null && \
1114 tar -xpf "$file"
1115 chkstatus_or_exit 3
1117 *.tar.gz | *.tgz | *.tar.Z )
1118 gzip -cd "$file" | tar -tf - > /dev/null && \
1119 gzip -cd "$file" | tar -xpf -
1120 chkstatus_or_exit 3
1122 *.tar.bz2 | *.tbz2 | *.tbz )
1123 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1124 bzip2 -cd "$file" | tar -xpf -
1125 chkstatus_or_exit 3
1127 *.tar.lz | *.tlz )
1128 lzip -cd "$file" | tar -tf - > /dev/null && \
1129 lzip -cd "$file" | tar -xpf -
1130 chkstatus_or_exit 3
1132 *.tar.xz | *.txz )
1133 xz -cd "$file" | tar -tf - > /dev/null && \
1134 xz -cd "$file" | tar -xpf -
1135 chkstatus_or_exit 3
1137 *.tar.zst | *.tzst )
1138 zstd -cd "$file" | tar -tf - > /dev/null && \
1139 zstd -cd "$file" | tar -xpf -
1140 chkstatus_or_exit 3
1142 *.zip | *.ZIP )
1143 unzip -t "$file" > /dev/null && \
1144 unzip "$file" > /dev/null
1145 chkstatus_or_exit 3
1147 *.gz)
1148 gzip -t "$file" && \
1149 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1150 chkstatus_or_exit 3
1152 *.Z)
1153 gzip -t "$file" && \
1154 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1155 chkstatus_or_exit 3
1157 *.bz2)
1158 bzip2 -t "$file" && \
1159 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1160 chkstatus_or_exit 3
1162 *.lz)
1163 lzip -t "$file" && \
1164 lzip -cd "$file" > "$(basename -- "$file" .lz)"
1165 chkstatus_or_exit 3
1167 *.xz)
1168 xz -t "$file" && \
1169 xz -cd "$file" > "$(basename -- "$file" .xz)"
1170 chkstatus_or_exit 3
1172 *.zst)
1173 zstd -qt "$file" && \
1174 zstd -cd "$file" > "$(basename -- "$file" .zst)"
1175 chkstatus_or_exit 3
1178 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1179 exit 1
1180 esac
1181 done
1182 unset -v file
1185 do_meta()
1187 # Extract information from the recipe to create the meta file.
1189 # The package description is pre-formatted in 78 columns,
1190 # the '#' character and a space is added as prefix to conform
1191 # the 80 columns in total
1192 printf '%s\n' "$description" | fold -w 78 | awk '$0="# " $0'
1194 # Include build flags only if it is a real architecture
1195 if test "$arch" != noarch
1196 then
1197 printf '%s' \
1199 QICFLAGS=\"$QICFLAGS\"
1200 QICXXFLAGS=\"$QICXXFLAGS\"
1201 QILDFLAGS=\"$QILDFLAGS\"
1202 QICPPFLAGS=\"$QICPPFLAGS\""
1205 # Print saving the rest of the package information
1206 printf '%s' \
1208 pkgname=$pkgname
1209 pkgversion=$pkgversion
1210 arch=$arch
1211 release=$release
1212 pkgcategory=\"${pkgcategory#@*}\"
1213 full_pkgname=$full_pkgname
1214 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1215 homepage=\"$homepage\"
1216 license=\"$license\"
1217 fetch=\"$fetch\"
1218 replace=\"$replace\"
1222 ### Default values
1224 packagedir=@PACKAGEDIR@
1225 targetdir=@TARGETDIR@
1226 blacklist="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1227 _rcfile=@SYSCONFDIR@/qirc
1228 opt_install=opt_install.off
1229 opt_upgrade=opt_upgrade.off
1230 opt_force=opt_force.off
1231 opt_keep=opt_keep.off
1232 opt_incr_release=opt_incr_release.off
1233 opt_skipqsts=opt_skipqsts.off
1234 opt_nopkg=opt_nopkg.off
1235 opt_prune=opt_prune.off
1236 verbose_level=0
1237 opt_skiprecipe=""
1238 rootdir=""
1239 arch=@ARCH@
1240 jobs=1
1241 mode=""
1242 _xtrace_flag=""
1243 _readstdin=""
1244 graft_v=""
1245 graft_r=""
1246 _isUpgrade=_isUpgrade.off
1247 keep_srcdir=""
1248 keep_destdir=""
1249 TMPDIR="${TMPDIR:-/usr/src/qi/build}"
1250 QICFLAGS="${QICFLAGS:--O2}"
1251 QICXXFLAGS="${QICXXFLAGS:--O2}"
1252 QILDFLAGS="${QILDFLAGS:-}"
1253 QICPPFLAGS="${QICPPFLAGS:-}"
1254 worktree=/usr/src/qi
1255 tardir=${worktree}/sources
1256 outdir=/var/cache/qi/packages
1257 netget="wget2 -c -w1 -t3 --no-check-certificate"
1258 rsync="rsync -v -a -L -z -i --progress"
1259 tarlz_compression_options="-9 --solid"
1260 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1261 infodir=@INFODIR@
1262 mandir=@MANDIR@
1263 docdir=@DOCDIR@
1265 # Store (default) directory locations
1266 QI_TARGETDIR=$targetdir
1267 QI_PACKAGEDIR=$packagedir
1268 QI_WORKTREE=$worktree
1269 QI_TARDIR=$tardir
1270 QI_OUTDIR=$outdir
1272 ### Parse commands and options
1274 validate_mode()
1276 if test -n "$mode"
1277 then
1278 warn "${PROGRAM}: First defined command: ${mode#*_}" \
1279 "Switching to another command is not allowed (${1})."
1280 exit 1
1284 validate_option()
1286 if test -z "$2"
1287 then
1288 warn "${PROGRAM}: The '${1}' option requires an argument" \
1289 "Try '${PROGRAM} --help' for more information."
1290 exit 1
1294 validate_directory()
1296 if test ! -d "$2"
1297 then
1298 warn "${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1299 exit 1
1303 validate_digit()
1305 name="$1"
1306 shift
1308 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1309 case "${1#[-+]}" in
1311 warn "${PROGRAM}: The '${name}' option has no defined value"
1312 exit 1
1314 *.*.*)
1315 warn "${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1316 exit 1
1318 *[!0-9]*)
1319 warn "${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1320 exit 1
1322 esac
1324 unset -v name
1327 while test $# -gt 0
1329 case $1 in
1330 warn)
1331 validate_mode warn
1332 mode=mode_warn
1334 install)
1335 validate_mode install
1336 readconfig
1337 mode=mode_install
1339 remove)
1340 validate_mode remove
1341 readconfig
1342 mode=mode_remove
1344 upgrade)
1345 validate_mode upgrade
1346 readconfig
1347 mode=mode_upgrade
1349 extract)
1350 validate_mode extract
1351 mode=mode_extract
1353 create)
1354 validate_mode create
1355 mode=mode_create
1357 order)
1358 validate_mode order
1359 mode=mode_order
1361 build)
1362 validate_mode build
1363 readconfig
1364 mode=mode_build
1366 --no-rc | -N )
1367 _readconfig=readconfig.off
1369 --install | -i )
1370 opt_install=opt_install
1372 --upgrade | -u )
1373 opt_upgrade=opt_upgrade
1375 --force | -f )
1376 opt_force=opt_force
1378 --keep | -k )
1379 opt_keep=opt_keep
1381 --prune | -p )
1382 opt_prune=opt_prune
1384 --packagedir | -P )
1385 validate_option "$1" "$2"
1386 packagedir="$2"
1387 validate_directory "$1" "$packagedir"
1388 shift
1390 --packagedir=*)
1391 validate_option "$1" "$2"
1392 packagedir="${1#*=}"
1393 validate_directory "$1" "$packagedir"
1395 --targetdir | -t )
1396 validate_option "$1" "$2"
1397 targetdir="$2"
1398 validate_directory "$1" "$targetdir"
1399 shift
1401 --targetdir=*)
1402 validate_option "$1" "$2"
1403 targetdir="${1#*=}"
1404 validate_directory "$1" "$targetdir"
1406 --rootdir | -r )
1407 validate_option "$1" "$2"
1408 rootdir="$2"
1409 validate_directory "$1" "$rootdir"
1410 shift
1412 --rootdir=*)
1413 validate_option "$1" "$2"
1414 rootdir="${1#*=}"
1415 validate_directory "$1" "$rootdir"
1417 --outdir | -o )
1418 validate_option "$1" "$2"
1419 outdir="$2"
1420 validate_directory "$1" "$outdir"
1421 shift
1423 --outdir=*)
1424 validate_option "$1" "$2"
1425 outdir="${1#*=}"
1426 validate_directory "$1" "$outdir"
1428 --worktree | -w )
1429 validate_option "$1" "$2"
1430 worktree="$2"
1431 validate_directory "$1" "$worktree"
1432 shift
1434 --worktree=*)
1435 validate_option "$1" "$2"
1436 worktree="${1#*=}"
1437 validate_directory "$1" "$worktree"
1439 --sourcedir | -s )
1440 validate_option "$1" "$2"
1441 tardir="$2"
1442 validate_directory "$1" "$tardir"
1443 shift
1445 --sourcedir=*)
1446 validate_option "$1" "$2"
1447 tardir="${1#*=}"
1448 validate_directory "$1" "$tardir"
1450 --architecture | -a )
1451 validate_option "$1" "$2"
1452 arch="$2"
1453 shift
1455 --arch=*)
1456 validate_option "$1" "$2"
1457 arch="${1#*=}"
1459 --jobs | -j )
1460 jobs="$2"
1461 validate_digit "$1" "$jobs"
1462 shift
1464 -j[0-9]*)
1465 jobs="${1#-j*}"
1466 validate_digit '-j' "$jobs"
1468 --jobs=*)
1469 jobs="${1#*=}"
1470 validate_digit '--jobs=' "$jobs"
1472 --no-package | -n )
1473 opt_nopkg=opt_nopkg
1475 --increment | -1 )
1476 opt_incr_release=opt_incr_release
1478 --skip-questions | -S )
1479 opt_skipqsts=opt_skipqsts
1481 --verbose | -v )
1482 verbose_level=$(( verbose_level + 1 ))
1484 -vv)
1485 # A trick for a second -v.
1486 verbose_level=2
1488 --show-location | -L )
1489 printf '%s\n' \
1490 "QI_TARGETDIR=$QI_TARGETDIR" \
1491 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1492 "QI_WORKTREE=$QI_WORKTREE" \
1493 "QI_TARDIR=$QI_TARDIR" \
1494 "QI_OUTDIR=$QI_OUTDIR"
1495 exit 0
1497 --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' | \
1498 help )
1499 usage
1500 exit 0
1502 --version | --versio | --versi | --vers | \
1503 -version | -versio | -versi | -vers | -V | version )
1504 echo "$PROGRAM version @VERSION@"
1505 exit 0
1507 '-')
1508 _readstdin=readstdin
1509 break
1512 shift
1513 break; # End of options.
1516 warn "qi: Unrecognized option: $1" \
1517 "Try '${PROGRAM} --help' for more information."
1518 exit 1
1521 break; # No more options.
1523 esac
1524 shift
1525 done
1526 unset -f readconfig validate_mode validate_option validate_directory validate_digit
1528 # When there are no arguments, show the help
1529 if test $# -eq 0
1530 then
1531 usage
1532 exit 1
1534 unset -f usage
1536 # Program sanity check
1538 for need in awk basename chmod cp dirname find fold graft grep \
1539 mkdir mktemp rm rmdir sed sha256sum stat tarlz ; \
1541 if ! \command -v $need > /dev/null
1542 then
1543 warn "${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1544 exit 2
1546 done
1547 unset -v need
1549 # Set verbosity level and flags
1551 if test "$verbose_level" -gt 0
1552 then
1553 if test "$verbose_level" -eq 1
1554 then
1555 graft_v=-v
1556 else
1557 graft_v=-V
1561 # Read standard input if FILE is -, or when
1562 # FILE is not connected to a terminal
1564 if test "$_readstdin" = readstdin
1565 then
1566 if test -t 0
1567 then
1568 warn "qi: I won't read from a connected terminal." \
1569 "Try '${PROGRAM} --help' for more information."
1570 exit 1
1573 # Unset positional parameters setting $# to zero
1574 set --
1576 # Assign remaining arguments to the positional parameters
1577 while read -r input
1579 set -- "$@" "$input"
1580 done
1582 unset -v _readstdin
1584 if test -z "$mode"
1585 then
1586 warn "qi: We need at least one (valid) command." \
1587 "Try '${PROGRAM} --help' for more information."
1588 exit 4
1591 # Validate 'packagedir' and 'targetdir' as canonical directories
1593 # The single slash '/' does not qualify here
1594 if ! fnmatch '/?*' "$packagedir"
1595 then
1596 warn "${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1597 exit 4
1599 if test ! -d "$packagedir"
1600 then
1601 warn "${PROGRAM}: Package directory \`${packagedir}' does not exist"
1602 exit 4
1605 # The single slash '/' is valid here
1606 if ! fnmatch '/*' "$targetdir"
1607 then
1608 warn "${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1609 exit 4
1611 if test ! -d "$targetdir"
1612 then
1613 warn "${PROGRAM}: Target directory \`${targetdir}' does not exist"
1614 exit 4
1617 # Validate 'rootdir' directory
1619 if test -n "$rootdir"
1620 then
1621 if test -d "$rootdir" && test "$rootdir" != /
1622 then
1623 rootdir="${rootdir%/}" # Remove slash from the end.
1625 # A workaround for graft-2.13+. The specified directory is
1626 # relative to the log file, we prepend it inside 'rootdir'
1628 eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
1629 mkdir -p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1631 # Compose 'rootdir' and log file option to be used with graft(1)
1632 graft_r="-r $rootdir -l $GRAFT_LOGFILE"
1634 # Unset variables coming from eval
1635 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1636 else
1637 warn "${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1638 exit 4
1640 readonly rootdir
1641 export rootdir
1644 # Ensure 'TMPDIR' creation to prefix temporary files
1646 if test ! -d "$TMPDIR"
1647 then
1648 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1650 readonly TMPDIR packagedir targetdir
1652 # Process each package or recipe provided on the command-line
1654 for package in "$@"
1656 $mode "$package"
1657 done