2 # Copyright (C) 2016-2021 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/>.
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
29 LC_ALL
=C
; # Override locale settings.
30 umask 022; # Remove write permission for group and other.
38 "Usage: $PROGRAM COMMAND [OPTIONS] [FILE]...
39 "'A simple but well-integrated package manager.
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
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
105 printf '%s\n' "$@" 1>&2
114 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
118 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
123 # Portable alternative to the file operator -nt (among shells)
126 if test -n "$(find "$1" -prune -newer "$2" -print)"
133 # Determine whether $2 matches pattern $1
150 if test $status -ne 0
152 echo "^ Return status = $status" 1>&2
153 exit "${1-2}"; # If not given, defaults to 2
161 if test "${_readconfig:-readconfig}" = readconfig
163 is_readable
"$HOME/.qirc" 2> /dev
/null
&& _rcfile
="$HOME/.qirc";
164 echo "Importing configuration file from \`${_rcfile}' ..."
165 .
"$_rcfile" || chkstatus_or_exit
5
177 echo "{@} Building \"${recipe}\" ..."
179 # A recipe is any valid regular file. Qi sets priorities for reading
180 # a recipe, the order in which qi looks for a recipe is:
182 # 1. Current working directory.
184 # 2. If the specified path name does not contain "recipe" as the last
185 # component. Qi will complete it by adding "recipe" to the path
188 # 3. If the recipe is not in the current working directory, it will be
189 # searched under '${worktree}/recipes'. The last component will be
190 # completed adding "recipe" to the specified path name.
192 if test ! -f "$recipe"
194 if test -f "${recipe}/recipe"
196 recipe
="${recipe}/recipe"
197 elif test -f "${worktree}/recipes/${recipe}/recipe"
199 recipe
="${worktree}/recipes/${recipe}/recipe"
203 test -f "$recipe" ||
{
204 warn
"\`${recipe}' is not a regular file."
208 # Complain if the file name is not "recipe"
210 if test "${recipe##*/}" != recipe
212 warn
"\`${recipe}' is not a valid recipe name."
216 # Start preparations to import the recipe
218 # Get working directory and base name of the recipe
220 CWD
="$(CDPATH='' cd -P -- "$
(dirname -- "$recipe")" && pwd -P)"
221 recipe
="$(basename -- "$recipe")"
223 # Check readability for load the recipe on success
225 is_readable
"${CWD}/$recipe" ||
exit 4
227 # Re-create external directories
228 mkdir
-p -- "${worktree}/archive" \
229 "${worktree}/patches" \
230 "${worktree}/recipes" \
233 # Variables treatment for the current and the next recipe.
235 # Unset special variables that can only be predefined in
236 # the recipe and does not come from '${sysconfdir}/qirc'
239 srcdir destdir pkgname pkgversion pkgcategory program version release \
240 fetch description homepage license replace full_pkgname docs docsdir \
241 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
243 # The following variables must be restored, later
244 save_arch
="${save_arch:=$arch}"
245 save_jobs
="${save_jobs:=$jobs}"
246 save_outdir
="${save_outdir:=$outdir}"
247 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
249 # Reset variable values in case of return
252 outdir
="$save_outdir"
253 opt_nopkg
="$save_opt_nopkg"
255 # The following variables cannot be redefined on the recipe
256 readonly worktree netget rsync
263 # Check for required variables
264 if test -z "$program"
266 warn
"${recipe}: The variable 'program' is not defined."
269 if test -z "$version"
271 warn
"${recipe}: The variable 'version' is not defined."
274 if test -z "$release"
276 warn
"${recipe}: The variable 'release' is not defined."
280 # Pre-settings before to start building
282 # Increment the release number if the option was given
283 if test "$opt_incr_release" = opt_incr_release
285 release
=$
(( release
+ 1 ))
288 # Allow the dot as definition for 'tardir'
289 if test "$tardir" = .
294 # Set default values for the following special variables
296 pkgname
="${pkgname:=$program}"
297 pkgversion
="${pkgversion:=$version}"
298 srcdir
="${srcdir:=${program}-$version}"
299 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
301 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
302 if test -n "$pkgcategory"
304 pkgcategory
="@${pkgcategory}"
307 # Compose the full package name
308 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
310 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
311 outdir
="${outdir}/${arch}"
313 # If a package is going to be created the existence of a
314 # previous build will be detected and reported. Under normal
315 # conditions the recipe is built as long as it is newer than
316 # the produced package, if not, we warn to the user about it.
317 # Rebuilding the package is possible (through the force ;-)
319 if test "$opt_nopkg" != opt_nopkg
&& \
320 { test "$opt_force" != opt_force
&& \
321 test -e "${outdir}/${full_pkgname}.tlz" ; }
323 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
327 "This recipe is
more RECENT than the produced package
:" \
329 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
330 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
332 " This recipe will be processed ...
" \
334 elif test -e "${CWD}/post-install
" && \
335 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
339 "The post-install
script is
more RECENT than the recipe
:" \
341 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
342 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
344 " This recipe will be re-processed ...
" \
346 touch "${CWD}/$recipe"
347 elif test "$opt_skipqsts" = opt_skipqsts
349 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
354 "This recipe ALREADY produced a package
:" \
355 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
357 "The recipe is still OLDER than the produced package
:" \
358 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
360 " Probably nothing has changed.
" \
363 # In non-interactive mode, the user is asked about
364 # rebuilding the package. In interactive mode,
365 # the user need to pass the option explicitly
369 "Do you want to rebuild this package?
" \
371 "2) No
, skip it
[default
]" \
372 "3) Resume
, skipping completed recipes
" \
373 "Enter an option number
:" > /dev/tty
374 IFS= read -r ANSWER < /dev/tty || exit 2;
377 echo "$ANSWER" > /dev/tty
382 echo "=== Building UNPROCESSED
/MODIFIED recipes ...
" > /dev/tty
384 opt_skipqsts=opt_skipqsts
385 readonly opt_skipqsts
390 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
396 warn "Use the
--force option to reprocess
${CWD}/$recipe.
" ""
402 # Fetch remote sources
404 echo "=== Fetching remote sources
if needed ...
"
409 _source="${origin##*/}"
411 echo "=== Looking
for \"$_source\" ...
"
413 echo "=== Verifying checksum
file \
`${tardir}/${_source}.sha256'"
414 if test -e "${tardir}/${_source}.sha256"
416 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
420 warn "${_source}.sha256: Checksum file does not exist, yet."
423 # Download source or resume if allowed
425 if test ! -e "${tardir}/$_source"
427 warn " Can't find \"${_source}\" at \"${tardir}\";" \
428 "attempting to get it from ${origin%/*} ..."
434 cd -- "$tardir" && $rsync "$origin" || exit $?
435 sha256sum "$_source" > "${_source}.sha256"
436 ); chkstatus_or_exit 10
440 cd -- "$tardir" && $netget "$origin" || exit $?
441 sha256sum "$_source" > "${_source}.sha256"
442 ); chkstatus_or_exit 10
445 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
449 unset -v origin _source
451 warn "${recipe}: The variable 'fetch' is empty."
454 # Prepare special directories for build the source,
455 # the destination and the output of the package
457 echo "=== Preparing directories ..."
459 if test -z "$keep_srcdir"
461 if test -e "${TMPDIR}/$srcdir"
463 rm -r -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
464 echo "removed directory: '${TMPDIR}/$srcdir'"
468 "WARNING: The variable 'keep_srcdir' has been set (${keep_srcdir})."
471 if test -z "$keep_destdir"
473 if test -e "$destdir"
475 rm -r -- "$destdir" || chkstatus_or_exit
476 echo "removed directory: '$destdir'"
478 mkdir -p -- "$destdir" || chkstatus_or_exit
479 echo "mkdir: created directory '$destdir'"
482 if test ! -e "$outdir"
484 mkdir -p -- "$outdir" || chkstatus_or_exit
485 echo "mkdir: created directory '$outdir'"
488 echo "=== Changing to '${TMPDIR}' ..."
489 cd -- "$TMPDIR" || chkstatus_or_exit
491 # Set trap before to run the build() function in order
492 # to catch the return status, exit code 2 if fails
494 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
496 # Determine if the debugging indicators of the shell should be
497 # retained, assuming that it has been previously passed
499 _xtrace_flag=_xtrace_flag_is_set ;;
502 echo "=== Running the 'build' function ..."
506 # Check recipe to run (extra) defined functions by the packager
508 $(awk '!/^build[ (]/ && /^[^ {}]+ *\(\)/{ gsub(/[()]/, "", $1); print $1 }' "${CWD}/$recipe")
510 # Check if it is a shell function
511 case $(LC_ALL=C type $_definition) in
513 # Call and undo the function after executing it
515 unset -f $_definition
521 # Turn off possible shell flags coming from the recipe
524 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
529 # Reset given signals
530 trap - EXIT HUP INT QUIT ABRT TERM
532 # If 'destdir' is empty, the package won't be created
533 if rmdir -- "$destdir" 2> /dev/null
535 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
539 # Create (make) the package
541 if test "$opt_nopkg" != opt_nopkg
543 # Edit the recipe when 'release' is incremented
544 if test "$opt_incr_release" = opt_incr_release
546 echo ",s/^\\(release\\)=.*/\\1=${release}/"$'\nw' | \
547 ed "${CWD}/$recipe" || chkstatus_or_exit
550 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
552 # Include a copy of the recipe into the package
553 cp -p "${CWD}/$recipe" \
554 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
555 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
558 # Detect post-install script for inclusion
560 if test -f "${CWD}/post-install"
562 echo "${CWD}/post-install: Detected."
563 cp -p "${CWD}/post-install" \
564 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
565 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
569 # Detect declared package name(s) for later replacement
571 if test -n "$replace"
574 "=== The following package names has been declared for replacement:" \
577 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
580 printf '%s\n' "$replace" >> \
581 "${destdir}/var/lib/qi/${full_pkgname}.replace"
586 # Create (external) meta file for package information,
587 # make a copy of it for the package database
588 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
589 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
590 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
591 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
593 # Produce the package
594 cd -- "$destdir" && mode_create "${outdir}/${full_pkgname}.tlz"
597 # Back to the current working directory
598 cd -- "$CWD" || chkstatus_or_exit
600 echo "=== Deleting 'srcdir', 'destdir' ..."
601 if test "$opt_keep" != opt_keep
603 if test -z "$keep_srcdir"
605 srcdir="${srcdir%%/*}"; # Directory name without parents.
606 if test -e "${TMPDIR}/$srcdir"
608 rm -r -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
609 echo "removed directory: '${TMPDIR}/$srcdir'"
613 if test -z "$keep_destdir"
615 if test -e "$destdir"
617 rm -r -- "$destdir" || chkstatus_or_exit
618 echo "removed directory: '$destdir'"
623 " Request via --keep to preserve 'srcdir' or 'destdir' has been made:" \
624 "${TMPDIR}/$srcdir" \
628 # Install or upgrade the package after build
629 if test "$opt_nopkg" != opt_nopkg
631 if test "$opt_install" = opt_install
633 mode_install "${outdir}/${full_pkgname}.tlz"
634 elif test "$opt_upgrade" = opt_upgrade
636 mode_upgrade "${outdir}/${full_pkgname}.tlz"
640 echo "{@} All done for \"${CWD}/${recipe}\"."
646 directory="$(dirname -- "$1")"
648 # Perform sanity checks
650 if ! fnmatch '/?*' "$directory"
652 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
655 is_readable "$directory" || exit 4
657 name="$(basename -- "$1")"
660 echo "{#} Creating package name \`${name}' ...
"
662 if test "$name" = "${name%.tlz}"
664 warn "Package format
'$name' not supported.
" \
665 "It should be
\"name_version_architecture-release
[@pkgcategory
].tlz
\""
669 # Pass extra options for tarlz(1)
670 if test -n "$SOURCE_DATE_EPOCH"
672 tarlz_opts="--mtime=@
${SOURCE_DATE_EPOCH}"
675 ( umask 022 ; tarlz --solid -9 $tarlz_opts -cvf - -- * ) \
676 > "${directory}/$name"
680 ( cd -- "$directory" && sha256sum "$name" > "${name}.sha256
" )
683 echo "{#} Package \"${name}\" created on ${directory}."
685 # Remove used variables
686 unset -v directory name
693 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
696 echo "<<< Removing package \`$rootdir${expunge}' ..."
698 # Complain if the package directory does not exist
700 test -e "$rootdir${expunge}" ||
{
701 warn
"Package directory '$rootdir${expunge}' does not exist."
705 # Complain if the package directory cannot be well-read
707 is_readable
"$rootdir${expunge}" ||
exit 4
709 # Remove package from Graft control
711 # Scan for possible conflicts, stop if arise
712 if test "$opt_prune" != opt_prune
714 echo "=== Checking for possible conflicts ..."
715 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
719 " A conflict occurred during uninstallation;" \
720 "Unless the --prune option is given, this package will be PRESERVED."
725 # Remove objects (files, links or directories) from the target
726 # directory that are in conflict with the package directory
728 echo "=== Pruning any conflict ..."
729 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
732 echo "=== Disabling links ..."
733 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
736 # Delete package directory
737 if test "$opt_keep" != opt_keep
739 echo "=== Deleting package directory, if exists as such ..."
740 if test -e "${rootdir}$expunge"
742 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
743 echo "removed directory: '${rootdir}$expunge'"
747 # Remove used variables
755 # Complain if the package cannot be well-read
757 is_readable
"$1" ||
exit 4
759 # Complain if the package does not end in .tlz
761 if ! fnmatch
'*.tlz' "$1"
763 warn
"\`${1}' does not end in .tlz"
767 # Make preparations to install the package
770 name
="$(basename -- "$1" .tlz)"
773 echo ">>> Installing package \`${name}' ..."
775 echo "=== Checking tarball integrity ..."
776 tarlz
--missing-crc -tf "$1" > /dev
/null
779 # To accept random directory from the upgrade mode
781 _packagedir
="${_packagedir:=$packagedir}"
783 # Create package directory using 'name'
784 if ! test -d "$rootdir${_packagedir}/$name"
786 mkdir
-p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
787 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
790 # Scan for possible conflicts, stop if arise
791 if test "$opt_prune" != opt_prune
793 echo "=== Checking for possible conflicts ..."
794 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
798 " A conflict occurred during installation;" \
799 "Unless the --prune option is given, this package won't be LINKED."
804 echo "=== Decompressing package ..."
805 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
808 # Transite package to Graft control
810 # Remove objects (files, links or directories) from the target
811 # directory that are in conflict with the package directory
812 echo "=== Pruning any conflict ..."
813 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
816 echo "=== Enabling symbolic links ..."
817 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
820 # Avoid unnecessary runs coming from the upgrade mode,
821 # this is when the incoming package is **pre-installed**
823 if test "$_isUpgrade" != _isUpgrade.on
825 # Show package description
826 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
828 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
829 elif test -r "${1}.txt"
831 # From external meta file (current directory)
832 awk '/^#/' "${1}.txt"
834 warn
"Description file not found for '$name'."
837 # Check and run the post-install script if exist
838 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
840 echo "=== Running post-install script for \`${name}' ..."
842 # Rely on 'targetdir' if 'rootdir' is empty
843 cd -- "${rootdir:=$targetdir}"/ && \
844 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
848 # Check if there are declared packages for replacement
849 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
853 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
855 if ! test -e "$replace"
857 warn
"<^> Declared package \`${replace}' does not exist. (ignoring)"
861 replace
="${replace##*/}"
863 # The search for the package to be replaced cannot
864 # be the same to the incoming package, even to the
865 # temporary location coming from the upgrade mode
866 if test "$replace" = "$name" || \
867 test "_x_${replace}" = "_x_${PRVLOC##*/}"
872 warn
"WARNING: Replacing package \`${replace}' ..."
874 # Since the links belongs to the new package, only
875 # those which are not in conflict can be deleted.
876 # To complete, we will remove the package directory
878 graft
-d -D -u $graft_r \
879 -t "$targetdir" "$replace" > /dev
/null
2>&1
881 rm -rf -- "$rootdir${_packagedir}/$replace"
883 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
887 # Remove used variables
888 unset -v name _packagedir
895 # Complain if the file cannot be well-read
897 is_readable
"$1" ||
exit 4
899 # Complain if the file does not end in .order
901 if ! fnmatch
'*.order' "$1"
903 warn
"\`${1}' does not end in .order"
907 # Get a clean list of the file while printing its contents in
908 # reverse order. Last awk(1) in the pipeline eliminates
909 # non-consecutive lines: duplicates, blank lines, and colons.
910 # Comment lines beginning with '#' are allowed; parentheses
914 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
915 for( i=NF; i > 0; i-- ) print $i
916 }' "$1" |
awk '!s[$0]++'
921 # Complain if the package does not end in .tlz
923 if ! fnmatch
'*.tlz' "$1"
925 warn
"\`${1}' does not end in .tlz"
930 incoming
="$(basename -- "$1" .tlz)"
933 echo "{%} Upgrading package \`${incoming}' ..."
935 # Check package pre-existence
936 if test "$opt_force" != opt_force
&& \
937 test -e "$rootdir${packagedir}/$incoming"
941 " The package to be upgraded already exist;" \
942 "Unless the --force option is given, this package won't be UPGRADED."
946 # Ignore some signals until the upgrade process is complete
947 trap "" HUP INT QUIT ABRT TERM
949 # Check blacklisted packages before to proceed with the upgrade
951 echo "=== Checking blacklist ..."
952 for item
in $blacklist
957 "A BLACKLISTED package name has been detected:" \
960 " This package will be INSTALLED instead of being upgraded."
961 opt_prune
=opt_prune mode_install
"$1"
968 # Prepare the package to install it in a temporary but custom location
970 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
971 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
973 echo "=== Pre-installing package in temporary location ..."
974 opt_prune
=opt_prune
# Turn on prune operation.
975 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
976 _isUpgrade
=_isUpgrade.off
978 echo "=== Looking for installations under the same name ..."
979 for long_name
in "$rootdir${packagedir}/${incoming%%_*}"_
*
981 # Check if it is a valid directory and if the incoming
982 # package is not equivalent to a previous installation;
983 # also check if the package to be deleted is equivalent
984 # to the temporary, private location.
986 if ! test -d "$long_name"
990 if test "${long_name##*/}" = "$incoming"
994 if test "$long_name" = "$PRVLOC"
999 warn
"Proceeding to the removal of ${long_name} ..."
1000 # A package directory will be preserved if --keep is given
1001 mode_remove
"${long_name##*/}" > /dev
/null
1005 # Re-install the package removing the temporary location
1008 opt_prune
=opt_prune.off
# Turn off prune operation.
1010 echo "=== Deleting temporary location ..."
1011 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1012 echo "removed directory: '$PRVLOC'"
1014 echo "{%} Upgraded from \"${1}\"."
1016 # Remove remaining variables
1017 unset -v incoming PRVLOC
1021 # Reset given signals
1022 trap - HUP INT QUIT ABRT TERM
1027 # Complain if the package cannot be well-read
1029 is_readable
"$1" ||
exit 4
1031 # Complain if the package does not end in .tlz
1033 if ! fnmatch
'*.tlz' "$1"
1035 warn
"\`${1}' does not end in .tlz"
1039 # List content of files excluding directories
1042 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1051 # Perform sanity checks before package extraction
1053 is_readable
"$1" ||
exit 4
1056 warn
"\`${1}' is not a regular file."
1060 # Preparations to extract the package
1062 name
="$(basename -- "$1" .tlz)"
1064 # Set random directory under 'TMPDIR' using 'name'
1065 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1067 # Trap to remove 'PRVDIR' on disruptions
1068 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1070 # Create 'PRVDIR' removing access for all but user
1071 ( umask 077 ; mkdir
-- "$PRVDIR" )
1072 mkdir
-p -- "$PRVDIR"
1073 chmod 700 -- "$PRVDIR"
1076 echo "--- Extracting package \`${name}' ..."
1078 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1081 # Try to remove (empty) 'PRVDIR' on failure
1087 echo "\"${name}\" has been extracted on \`${PRVDIR}'"
1089 # Reset given signals
1090 trap - HUP INT ABRT TERM
1092 # Remove used variables
1093 unset -v name PRVDIR
1096 ### Extra functions to be used during the modes
1104 tar -tf "$file" > /dev
/null
&& \
1108 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1109 gzip -cd "$file" |
tar -tf - > /dev
/null
&& \
1110 gzip -cd "$file" |
tar -xpf -
1113 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1114 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& \
1115 bzip2 -cd "$file" |
tar -xpf -
1119 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& \
1120 lzip
-cd "$file" |
tar -xpf -
1124 xz
-cd "$file" |
tar -tf - > /dev
/null
&& \
1125 xz
-cd "$file" |
tar -xpf -
1129 unzip -t "$file" > /dev
/null
&& \
1130 unzip "$file" > /dev
/null
1134 gzip -t "$file" && \
1135 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1139 gzip -t "$file" && \
1140 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1144 bzip2 -t "$file" && \
1145 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1149 lzip
-t "$file" && \
1150 lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1155 xz
-cd "$file" > "$(basename -- "$file" .xz)"
1159 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1168 # Extract information from the recipe to create the meta file.
1170 # The package description is pre-formatted in 78 columns,
1171 # the '#' character and a space is added as prefix to conform
1172 # the 80 columns in total
1173 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1175 # Include build flags only if it is a real architecture
1176 if test "$arch" != noarch
1180 QICFLAGS=\"$QICFLAGS\"
1181 QICXXFLAGS=\"$QICXXFLAGS\"
1182 QILDFLAGS=\"$QILDFLAGS\"
1183 QICPPFLAGS=\"$QICPPFLAGS\""
1186 # Print saving the rest of the package information
1190 pkgversion=$pkgversion
1193 pkgcategory=\"${pkgcategory#@*}\"
1194 full_pkgname=$full_pkgname
1195 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1196 homepage=\"$homepage\"
1197 license=\"$license\"
1199 replace=\"$replace\"
1205 packagedir
=@PACKAGEDIR@
1206 targetdir
=@TARGETDIR@
1207 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1208 _rcfile
=@SYSCONFDIR@
/qirc
1209 opt_install
=opt_install.off
1210 opt_upgrade
=opt_upgrade.off
1211 opt_force
=opt_force.off
1212 opt_keep
=opt_keep.off
1213 opt_incr_release
=opt_incr_release.off
1214 opt_skipqsts
=opt_skipqsts.off
1215 opt_nopkg
=opt_nopkg.off
1216 opt_prune
=opt_prune.off
1226 _isUpgrade
=_isUpgrade.off
1229 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1230 QICFLAGS
="${QICFLAGS:--O2}"
1231 QICXXFLAGS
="${QICXXFLAGS:--O2}"
1232 QILDFLAGS
="${QILDFLAGS:-}"
1233 QICPPFLAGS
="${QICPPFLAGS:-}"
1234 worktree
=/usr
/src
/qi
1235 tardir
=${worktree}/sources
1236 outdir
=/var
/cache
/qi
/packages
1237 netget
="wget -c -w1 -t3 --no-check-certificate"
1238 rsync
="rsync -v -a -L -z -i --progress"
1239 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1244 # Store (default) directory locations
1245 QI_TARGETDIR
=$targetdir
1246 QI_PACKAGEDIR
=$packagedir
1247 QI_WORKTREE
=$worktree
1251 ### Parse commands and options
1257 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1258 "Switching to another command is not allowed (${1})."
1267 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1268 "Try '${PROGRAM} --help' for more information."
1273 validate_directory
()
1277 warn
"${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1287 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1290 warn
"${PROGRAM}: The '${name}' option has no defined value"
1294 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1298 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1314 validate_mode
install
1319 validate_mode remove
1324 validate_mode upgrade
1329 validate_mode extract
1334 validate_mode create
1349 _readconfig
=readconfig.off
1352 opt_install
=opt_install
1355 opt_upgrade
=opt_upgrade
1367 validate_option
"$1" "$2"
1369 validate_directory
"$1" "$packagedir"
1373 validate_option
"$1" "$2"
1374 packagedir
="${1#*=}"
1375 validate_directory
"$1" "$packagedir"
1378 validate_option
"$1" "$2"
1380 validate_directory
"$1" "$targetdir"
1384 validate_option
"$1" "$2"
1386 validate_directory
"$1" "$targetdir"
1389 validate_option
"$1" "$2"
1391 validate_directory
"$1" "$rootdir"
1395 validate_option
"$1" "$2"
1397 validate_directory
"$1" "$rootdir"
1400 validate_option
"$1" "$2"
1402 validate_directory
"$1" "$outdir"
1406 validate_option
"$1" "$2"
1408 validate_directory
"$1" "$outdir"
1411 validate_option
"$1" "$2"
1413 validate_directory
"$1" "$worktree"
1417 validate_option
"$1" "$2"
1419 validate_directory
"$1" "$worktree"
1422 validate_option
"$1" "$2"
1424 validate_directory
"$1" "$tardir"
1428 validate_option
"$1" "$2"
1430 validate_directory
"$1" "$tardir"
1432 --architecture |
-a )
1433 validate_option
"$1" "$2"
1438 validate_option
"$1" "$2"
1443 validate_digit
"$1" "$jobs"
1448 validate_digit
'-j' "$jobs"
1452 validate_digit
'--jobs=' "$jobs"
1458 opt_incr_release
=opt_incr_release
1460 --skip-questions |
-S )
1461 opt_skipqsts
=opt_skipqsts
1464 verbose
=$
(( verbose
+ 1 ))
1467 # ^ Trick for a second -v.
1470 --show-location |
-L )
1472 "QI_TARGETDIR=$QI_TARGETDIR" \
1473 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1474 "QI_WORKTREE=$QI_WORKTREE" \
1475 "QI_TARDIR=$QI_TARDIR" \
1476 "QI_OUTDIR=$QI_OUTDIR"
1479 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' | \
1484 --version |
--versio |
--versi |
--vers | \
1485 -version |
-versio |
-versi |
-vers |
-V | version
)
1486 echo "$PROGRAM version @VERSION@"
1490 _readstdin
=readstdin
1495 break; # End of options.
1498 warn
"qi: Unrecognized option: $1" \
1499 "Try '${PROGRAM} --help' for more information."
1503 break; # No more options.
1509 readconfig validate_mode validate_option validate_directory validate_digit
1511 # When there are no arguments, show the help
1519 # Program sanity check
1521 for need
in awk basename chmod cp dirname find fold graft
grep \
1522 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1524 if ! \
command -v $need > /dev
/null
1526 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1532 # Set verbosity level/flag
1534 if test "$verbose" -gt 0
1536 if test "$verbose" -eq 1
1544 # Read standard input if FILE is -, or when
1545 # FILE is not connected to a terminal
1547 if test "$_readstdin" = readstdin
1551 warn
"qi: I won't read from a connected terminal." \
1552 "Try '${PROGRAM} --help' for more information."
1556 # Unset positional parameters setting $# to zero
1559 # Assign remaining arguments to the positional parameters
1562 set -- "$@" "$input"
1569 warn
"qi: We need at least one (valid) command." \
1570 "Try '${PROGRAM} --help' for more information."
1574 # Validate 'packagedir' and 'targetdir' as canonical directories
1576 # The single slash '/' does not qualify here
1577 if ! fnmatch
'/?*' "$packagedir"
1579 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1582 if test ! -d "$packagedir"
1584 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1588 # The single slash '/' is valid here
1589 if ! fnmatch
'/*' "$targetdir"
1591 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1594 if test ! -d "$targetdir"
1596 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1600 # Validate 'rootdir' directory
1602 if test -n "$rootdir"
1604 if test -d "$rootdir" && test "$rootdir" != /
1606 rootdir
="${rootdir%/}" # Remove slash from the end.
1608 # A workaround for graft-2.13+. The specified directory is
1609 # relative to the log file, we prepend it inside 'rootdir'
1611 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1612 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1614 # Compose 'rootdir' and log file option to be used with graft(1)
1615 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1617 # Unset variables coming from eval
1618 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1620 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1627 # Ensure 'TMPDIR' creation to prefix temporary files
1629 if test ! -d "$TMPDIR"
1631 mkdir
-p -- "$TMPDIR" || chkstatus_or_exit
1633 readonly TMPDIR packagedir targetdir
1635 # Process each package or recipe provided on the command-line