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/>.
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 if is_readable
"$HOME/.qirc" 2> /dev
/null
165 _rcfile
="$HOME/.qirc"
168 echo "Importing configuration file from \`${_rcfile}' ..."
169 .
"$_rcfile" || chkstatus_or_exit
5
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
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"
198 if test -f "${recipe}/recipe"
200 recipe
="${recipe}/recipe"
201 elif test -f "${worktree}/recipes/${recipe}/recipe"
203 recipe
="${worktree}/recipes/${recipe}/recipe"
207 test -f "$recipe" ||
{
208 warn
"\`${recipe}' is not a regular file."
212 # Complain if the file name is not "recipe"
214 if test "${recipe##*/}" != recipe
216 warn
"\`${recipe}' is not a valid recipe name."
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 # Re-create external directories
232 mkdir
-p -- "${worktree}/archive" \
233 "${worktree}/patches" \
234 "${worktree}/recipes" \
237 # Variables treatment for the current and the next recipe.
239 # Unset special variables that can only be predefined in
240 # the recipe and does not come from '${sysconfdir}/qirc'
243 srcdir destdir pkgname pkgversion pkgcategory program version release \
244 fetch description homepage license replace full_pkgname docs docsdir \
245 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
247 # The following variables must be restored, later
248 save_arch
="${save_arch:=$arch}"
249 save_jobs
="${save_jobs:=$jobs}"
250 save_outdir
="${save_outdir:=$outdir}"
251 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
253 # Reset variable values in case of return
256 outdir
="$save_outdir"
257 opt_nopkg
="$save_opt_nopkg"
259 # The following variables cannot be redefined on the recipe
260 readonly worktree netget rsync
267 # Check for required variables
268 if test -z "$program"
270 warn
"${recipe}: The variable 'program' is not defined."
273 if test -z "$version"
275 warn
"${recipe}: The variable 'version' is not defined."
280 warn
"${recipe}: The variable 'arch' is not defined."
283 if test -z "$release"
285 warn
"${recipe}: The variable 'release' is not defined."
289 # Pre-settings before to start building
291 # Increment the release number if the option was given
292 if test "$opt_incr_release" = opt_incr_release
294 release
=$
(( release
+ 1 ))
297 # Allow the dot as definition for 'tardir'
298 if test "$tardir" = .
303 # Set default values for the following special variables
305 pkgname
="${pkgname:=$program}"
306 pkgversion
="${pkgversion:=$version}"
307 srcdir
="${srcdir:=${program}-$version}"
308 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
310 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
311 if test -n "$pkgcategory"
313 pkgcategory
="@${pkgcategory}"
316 # Compose the full package name
317 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
319 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
320 outdir
="${outdir}/${arch}"
322 # If a package is going to be created the existence of a
323 # previous build will be detected and reported. Under normal
324 # conditions the recipe is built as long as it is newer than
325 # the produced package, if not, we warn to the user about it.
326 # Rebuilding the package is possible (through the force ;-)
328 if test "$opt_nopkg" != opt_nopkg
&& \
329 { test "$opt_force" != opt_force
&& \
330 test -e "${outdir}/${full_pkgname}.tlz" ; }
332 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
336 "This recipe is
more RECENT than the produced package
:" \
338 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
339 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
341 " This recipe will be processed ...
" \
343 elif test -e "${CWD}/post-install
" && \
344 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
348 "The post-install
script is
more RECENT than the recipe
:" \
350 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
351 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
353 " This recipe will be re-processed ...
" \
355 touch "${CWD}/$recipe"
356 elif test "$opt_skipqsts" = opt_skipqsts
358 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
363 "This recipe ALREADY produced a package
:" \
364 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
366 "The recipe is still OLDER than the produced package
:" \
367 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
369 " Probably nothing has changed.
" \
372 # In non-interactive mode, the user is asked about
373 # rebuilding the package. In interactive mode,
374 # the user need to pass the option explicitly
378 "Do you want to rebuild this package?
" \
380 "2) No
, skip it
[default
]" \
381 "3) Resume
, skipping completed recipes
" \
382 "Enter an option number
:" > /dev/tty
383 IFS= read -r ANSWER < /dev/tty || exit 2;
386 echo "$ANSWER" > /dev/tty
391 echo "=== Building unprocessed or recently modified recipe
(s
) ...
" > /dev/tty
393 opt_skipqsts=opt_skipqsts
394 readonly opt_skipqsts
399 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
405 warn "Use the
--force option to reprocess
${CWD}/${recipe}.
" ""
411 # Fetch remote sources
413 echo "=== Fetching remote sources
if needed ...
"
418 echo "=== Looking
for $origin ...
"
420 _source="${origin##*/}"; # Get the file name.
422 echo "=== Verifying checksum
file found \
`${tardir}/${_source}.sha256'"
423 if test -e "${tardir}/${_source}.sha256"
425 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
429 warn " Checksum file \`${_source}.sha256
' does not exist"
432 # Download source or resume if allowed
434 if test ! -e "${tardir}/$_source"
436 warn "=== Attempting to get it from $origin ..."
442 cd -- "$tardir" && $rsync "$origin" || exit $?
443 sha256sum "$_source" > "${_source}.sha256"
444 ); chkstatus_or_exit 10
448 cd -- "$tardir" && $netget "$origin" || exit $?
449 sha256sum "$_source" > "${_source}.sha256"
450 ); chkstatus_or_exit 10
453 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
457 unset -v origin _source
459 warn "${recipe}: The variable 'fetch
' is empty."
462 # Prepare special directories for build the source,
463 # the destination and the output of the package
465 echo "=== Preparing directories ..."
467 if test -z "$keep_srcdir"
469 if test -e "${TMPDIR}/$srcdir"
471 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
472 echo "removed directory: '${TMPDIR}/$srcdir'"
476 "WARNING: The variable 'keep_srcdir
' has been set (${keep_srcdir})."
479 if test -z "$keep_destdir"
481 if test -e "$destdir"
483 rm -r -- "$destdir" || chkstatus_or_exit
484 echo "removed directory: '$destdir'"
486 mkdir -p -- "$destdir" || chkstatus_or_exit
487 echo "mkdir: created directory '$destdir'"
490 if test ! -e "$outdir"
492 mkdir -p -- "$outdir" || chkstatus_or_exit
493 echo "mkdir: created directory '$outdir'"
496 echo "=== Changing to '${TMPDIR}' ..."
497 cd -- "$TMPDIR" || chkstatus_or_exit
499 # Set trap before to run the build() function in order
500 # to catch the return status, exit code 2 if fails
502 trap 'chkstatus_or_exit
2' EXIT HUP INT QUIT ABRT TERM
504 # Determine if the debugging indicators of the shell should be
505 # retained, assuming that it has been previously passed
507 _xtrace_flag=_xtrace_flag_is_set ;;
510 echo "=== Running the 'build
' function ..."
514 # Check recipe to run (extra) defined functions by the packager
516 $(awk '!/^build
[ (]/ && /^
[^
{}]+ *\
(\
)/{ gsub
(/[()]/, "", $1); print
$1 }' "${CWD}/$recipe")
518 # Check if it is a shell function
519 case $(LC_ALL=C type $_definition) in
521 # Call and undo the function after executing it
523 unset -f $_definition
529 # Turn off possible shell flags coming from the recipe
532 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
537 # Reset given signals
538 trap - EXIT HUP INT QUIT ABRT TERM
540 # If 'destdir
' is empty, the package won't be created
541 if rmdir -- "$destdir" 2> /dev
/null
543 warn
"The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
547 # Create (make) the package
549 if test "$opt_nopkg" != opt_nopkg
551 # Edit the recipe when 'release' is incremented
552 if test "$opt_incr_release" = opt_incr_release
554 echo ",s/^\\(release\\)=.*/\\1=${release}/"$
'\nw' | \
555 ed
"${CWD}/$recipe" || chkstatus_or_exit
558 mkdir
-p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
560 # Include a copy of the recipe into the package
561 cp -p "${CWD}/$recipe" \
562 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
563 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
566 # Produce a checksum file for recipe copy
567 cd -- "${destdir}/var/lib/qi" && \
568 sha256sum
"${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
569 ); chkstatus_or_exit
4
571 # Detect post-install script for inclusion
573 if test -f "${CWD}/post-install"
575 echo "${CWD}/post-install: Detected."
576 cp -p "${CWD}/post-install" \
577 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
578 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
582 # Detect declared package name(s) for later replacement
584 if test -n "$replace"
587 "=== The following package names has been declared for replacement:" \
590 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
593 printf '%s\n' "$replace" >> \
594 "${destdir}/var/lib/qi/${full_pkgname}.replace"
599 # Create (external) meta file for package information,
600 # make a copy of it for the package database
601 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
602 do_meta
> "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
603 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
604 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
606 # Produce the package
607 cd -- "$destdir" && mode_create
"${outdir}/${full_pkgname}.tlz"
610 # Back to the current working directory
611 cd -- "$CWD" || chkstatus_or_exit
613 echo "=== Deleting 'srcdir' or 'destdir' ..."
614 if test "$opt_keep" != opt_keep
616 if test -z "$keep_srcdir"
618 srcdir
="${srcdir%%/*}"; # Directory name without parents.
619 if test -e "${TMPDIR}/$srcdir"
621 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
622 echo "removed directory: '${TMPDIR}/$srcdir'"
625 if test -z "$keep_destdir"
627 if test -e "$destdir"
629 rm -rf -- "$destdir" || chkstatus_or_exit
630 echo "removed directory: '$destdir'"
635 " The following directories will be preserved:" \
636 "${TMPDIR}/$srcdir" \
639 "The '--keep' option has been used."
642 # Install or upgrade the package after build
643 if test "$opt_nopkg" != opt_nopkg
645 if test "$opt_install" = opt_install
647 mode_install
"${outdir}/${full_pkgname}.tlz"
648 elif test "$opt_upgrade" = opt_upgrade
650 mode_upgrade
"${outdir}/${full_pkgname}.tlz"
654 warn
"{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
659 directory
="$(dirname -- "$1")"
661 # Perform sanity checks
663 if ! fnmatch
'/?*' "$directory"
665 warn
"${PROGRAM}: Output directory \`${directory}' is not fully qualified"
668 is_readable
"$directory" ||
exit 4
670 name
="$(basename -- "$1")"
673 echo "{#} Creating package name \`${name}' ..."
675 if test "$name" = "${name%.tlz}"
677 warn
"Package format '$name' not supported." \
678 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
682 # Pass extra options to tarlz(1)
683 if test -n "$SOURCE_DATE_EPOCH"
685 tarlz_opts
="--mtime=@${SOURCE_DATE_EPOCH}"
688 ( umask 022 ; tarlz
--solid -9 $tarlz_opts -cvf - -- * ) \
689 > "${directory}/$name"
693 ( cd -- "$directory" && sha256sum
"$name" > "${name}.sha256" )
696 warn
"{#} Package \"${name}\" created on ${directory}." ""
698 # Remove used variables
699 unset -v directory name
704 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
707 echo "{<} Removing \`$rootdir${expunge}' ..."
709 # Complain if the package directory does not exist
711 test -e "$rootdir${expunge}" ||
{
712 warn
"Package directory '$rootdir${expunge}' does not exist."
716 # Complain if the package directory cannot be well-read
718 is_readable
"$rootdir${expunge}" ||
exit 4
720 # Remove package from Graft control
722 # Scan for possible conflicts, stop if arise
723 if test "$opt_prune" != opt_prune
725 echo "=== Checking for possible conflicts ..."
726 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
730 " A conflict occurred during uninstallation;" \
731 "Unless the --prune option is given, this package will be PRESERVED."
736 # Remove objects (files, links or directories) from the target
737 # directory that are in conflict with the package directory
739 echo "=== Pruning any conflict ..."
740 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
743 echo "=== Disabling links ..."
744 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
747 # Delete package directory
748 if test "$opt_keep" != opt_keep
750 echo "=== Deleting package directory ..."
751 if is_readable
"${rootdir}$expunge"
753 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
754 echo "removed directory: '${rootdir}$expunge'"
758 warn
"{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
764 # Complain if the package cannot be well-read
766 is_readable
"$1" ||
exit 4
768 # Complain if the package does not end in .tlz
770 if ! fnmatch
'*.tlz' "$1"
772 warn
"\`${1}' does not end in .tlz"
776 # Make preparations to install the package
779 echo "{>} Installing $1 ..."
781 echo "=== Checking tarball integrity ..."
782 tarlz
--missing-crc -tf "$1" > /dev
/null
785 # To accept random directory from the upgrade mode
787 _packagedir
="${_packagedir:=$packagedir}"
789 # Create package directory using 'name'
791 name
="$(basename -- "$1" .tlz)"; # Get the file name.
793 if ! test -d "$rootdir${_packagedir}/$name"
795 mkdir
-p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
796 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
799 # Scan for possible conflicts, stop if arise
800 if test "$opt_prune" != opt_prune
802 echo "=== Checking for possible conflicts ..."
803 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
807 " A conflict occurred during installation;" \
808 "Unless the --prune option is given, this package won't be LINKED."
813 echo "=== Decompressing package ..."
814 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
817 # Transite package to Graft control
819 # Remove objects (files, links or directories) from the target
820 # directory that are in conflict with the package directory
821 echo "=== Pruning any conflict ..."
822 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
825 echo "=== Enabling symbolic links ..."
826 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
829 # Avoid unnecessary runs coming from the upgrade mode,
830 # this is when the incoming package is **pre-installed**
832 if test "$_isUpgrade" != _isUpgrade.on
834 # Show package description
835 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
837 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
838 elif test -r "${1}.txt"
840 # From external meta file (current directory)
841 awk '/^#/' "${1}.txt"
843 warn
"Description file not found for '$name'."
846 # Check and run the post-install script if exist
847 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
849 echo "=== Running post-install script for \`${name}' ..."
851 # Rely on 'targetdir' if 'rootdir' is empty
852 cd -- "${rootdir:=$targetdir}"/ && \
853 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
857 # Check if there are declared packages for replacement
858 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
862 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
864 if ! test -e "$replace"
866 warn
" Declared package \`${replace}' to be replaced does not exist. [Ignored]"
870 replace
="${replace##*/}"
872 # The search for the package to be replaced cannot
873 # be the same to the incoming package, even to the
874 # temporary location coming from the upgrade mode
875 if test "$replace" = "$name" || \
876 test "_x_${replace}" = "_x_${PRVLOC##*/}"
881 warn
"WARNING: Replacing package \`${replace}' ..."
883 # Since the links belongs to the new package, only
884 # those which are not in conflict can be deleted.
885 # To complete, we will remove the package directory
887 graft
-d -D -u $graft_r \
888 -t "$targetdir" "$replace" > /dev
/null
2>&1
890 rm -rf -- "$rootdir${_packagedir}/$replace"
892 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
896 warn
"{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
897 unset -v name _packagedir
; # Remove used variables.
902 # Complain if the file cannot be well-read
904 is_readable
"$1" ||
exit 4
906 # Complain if the file does not end in .order
908 if ! fnmatch
'*.order' "$1"
910 warn
"\`${1}' does not end in .order"
914 # Get a clean list of the file while printing its contents in
915 # reverse order. Last awk(1) in the pipeline eliminates
916 # non-consecutive lines: duplicates, blank lines, and colons.
917 # Comment lines beginning with '#' are allowed; parentheses
921 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
922 for( i=NF; i > 0; i-- ) print $i
923 }' "$1" |
awk '!s[$0]++'
928 # Complain if the package does not end in .tlz
930 if ! fnmatch
'*.tlz' "$1"
932 warn
"\`${1}' does not end in .tlz"
937 echo "{^} Upgrading $1 ..."
939 incoming
="$(basename -- "$1" .tlz)"; # Get the file name.
941 # Check package pre-existence
942 if test "$opt_force" != opt_force
&& \
943 test -e "$rootdir${packagedir}/$incoming"
946 " The package to be upgraded already exist;" \
947 "Unless the --force option is given, this package won't be UPGRADED."
951 # Ignore some signals until the upgrade process is complete
952 trap "" HUP INT QUIT ABRT TERM
954 # Check blacklisted packages before to proceed with the upgrade
956 echo "=== Checking blacklist ..."
957 for item
in $blacklist
962 " A blacklisted package name has been detected:" \
965 "^ This package will be INSTALLED instead of being upgraded ..."
966 opt_prune
=opt_prune mode_install
"$1"
973 # Prepare the package to install it in a temporary but custom location
975 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
976 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
978 echo "=== Pre-installing package in temporary location ..."
980 opt_prune
=opt_prune
; # Turns on prune operation.
982 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
983 _isUpgrade
=_isUpgrade.off
985 echo "=== Looking for installations under the same name ..."
986 for previous_package
in "$rootdir${packagedir}/${incoming%%_*}"_
*
988 test -e "$previous_package" ||
continue
990 # Previous package directory to be removed cannot be
991 # the same to the temporary (private) location
992 if test "$previous_package" = "$PRVLOC"
997 # The directory of the package could be preserved if --keep
998 mode_remove
"$previous_package"
1000 unset -v previous_package
1002 # Re-install incoming package to the final location
1005 opt_prune
=opt_prune.off
; # Turns off prune operation.
1007 echo "=== Deleting temporary location ..."
1008 if is_readable
"$PRVLOC"
1010 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1011 echo "removed directory: '$PRVLOC'"
1015 warn
"{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1018 # Reset given signals
1019 trap - HUP INT QUIT ABRT TERM
1024 # Complain if the package cannot be well-read
1026 is_readable
"$1" ||
exit 4
1028 # Complain if the package does not end in .tlz
1030 if ! fnmatch
'*.tlz' "$1"
1032 warn
"\`${1}' does not end in .tlz"
1036 # List content of files excluding directories
1039 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1048 # Perform sanity checks before package extraction
1050 is_readable
"$1" ||
exit 4
1053 warn
"\`${1}' is not a regular file."
1057 # Preparations to extract the package
1059 name
="$(basename -- "$1" .tlz)"
1061 # Set random directory under 'TMPDIR' using 'name'
1062 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1064 # Trap to remove 'PRVDIR' on disruptions
1065 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1067 # Create 'PRVDIR' removing access for all but user
1068 ( umask 077 ; mkdir
-- "$PRVDIR" )
1069 mkdir
-p -- "$PRVDIR"
1070 chmod 700 -- "$PRVDIR"
1073 echo "{-} Extracting package \`${name}' ..."
1075 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1078 # Try to remove (empty) 'PRVDIR' on failure
1083 warn
"" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1085 # Remove used variables
1086 unset -v name PRVDIR
1088 # Reset given signals
1089 trap - HUP INT ABRT TERM
1092 ### Extra functions to be used during the modes
1100 tar -tf "$file" > /dev
/null
&& \
1104 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1105 gzip -cd "$file" |
tar -tf - > /dev
/null
&& \
1106 gzip -cd "$file" |
tar -xpf -
1109 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1110 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& \
1111 bzip2 -cd "$file" |
tar -xpf -
1115 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& \
1116 lzip
-cd "$file" |
tar -xpf -
1120 xz
-cd "$file" |
tar -tf - > /dev
/null
&& \
1121 xz
-cd "$file" |
tar -xpf -
1125 unzip -t "$file" > /dev
/null
&& \
1126 unzip "$file" > /dev
/null
1130 gzip -t "$file" && \
1131 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1135 gzip -t "$file" && \
1136 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1140 bzip2 -t "$file" && \
1141 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1145 lzip
-t "$file" && \
1146 lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1151 xz
-cd "$file" > "$(basename -- "$file" .xz)"
1155 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1164 # Extract information from the recipe to create the meta file.
1166 # The package description is pre-formatted in 78 columns,
1167 # the '#' character and a space is added as prefix to conform
1168 # the 80 columns in total
1169 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1171 # Include build flags only if it is a real architecture
1172 if test "$arch" != noarch
1176 QICFLAGS=\"$QICFLAGS\"
1177 QICXXFLAGS=\"$QICXXFLAGS\"
1178 QILDFLAGS=\"$QILDFLAGS\"
1179 QICPPFLAGS=\"$QICPPFLAGS\""
1182 # Print saving the rest of the package information
1186 pkgversion=$pkgversion
1189 pkgcategory=\"${pkgcategory#@*}\"
1190 full_pkgname=$full_pkgname
1191 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1192 homepage=\"$homepage\"
1193 license=\"$license\"
1195 replace=\"$replace\"
1201 packagedir
=@PACKAGEDIR@
1202 targetdir
=@TARGETDIR@
1203 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1204 _rcfile
=@SYSCONFDIR@
/qirc
1205 opt_install
=opt_install.off
1206 opt_upgrade
=opt_upgrade.off
1207 opt_force
=opt_force.off
1208 opt_keep
=opt_keep.off
1209 opt_incr_release
=opt_incr_release.off
1210 opt_skipqsts
=opt_skipqsts.off
1211 opt_nopkg
=opt_nopkg.off
1212 opt_prune
=opt_prune.off
1222 _isUpgrade
=_isUpgrade.off
1225 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1226 QICFLAGS
="${QICFLAGS:--O2}"
1227 QICXXFLAGS
="${QICXXFLAGS:--O2}"
1228 QILDFLAGS
="${QILDFLAGS:-}"
1229 QICPPFLAGS
="${QICPPFLAGS:-}"
1230 worktree
=/usr
/src
/qi
1231 tardir
=${worktree}/sources
1232 outdir
=/var
/cache
/qi
/packages
1233 netget
="wget2 -c -w1 -t3 --no-check-certificate"
1234 rsync
="rsync -v -a -L -z -i --progress"
1235 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1240 # Store (default) directory locations
1241 QI_TARGETDIR
=$targetdir
1242 QI_PACKAGEDIR
=$packagedir
1243 QI_WORKTREE
=$worktree
1247 ### Parse commands and options
1253 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1254 "Switching to another command is not allowed (${1})."
1263 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1264 "Try '${PROGRAM} --help' for more information."
1269 validate_directory
()
1273 warn
"${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1283 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1286 warn
"${PROGRAM}: The '${name}' option has no defined value"
1290 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1294 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1310 validate_mode
install
1315 validate_mode remove
1320 validate_mode upgrade
1325 validate_mode extract
1329 validate_mode create
1342 _readconfig
=readconfig.off
1345 opt_install
=opt_install
1348 opt_upgrade
=opt_upgrade
1360 validate_option
"$1" "$2"
1362 validate_directory
"$1" "$packagedir"
1366 validate_option
"$1" "$2"
1367 packagedir
="${1#*=}"
1368 validate_directory
"$1" "$packagedir"
1371 validate_option
"$1" "$2"
1373 validate_directory
"$1" "$targetdir"
1377 validate_option
"$1" "$2"
1379 validate_directory
"$1" "$targetdir"
1382 validate_option
"$1" "$2"
1384 validate_directory
"$1" "$rootdir"
1388 validate_option
"$1" "$2"
1390 validate_directory
"$1" "$rootdir"
1393 validate_option
"$1" "$2"
1395 validate_directory
"$1" "$outdir"
1399 validate_option
"$1" "$2"
1401 validate_directory
"$1" "$outdir"
1404 validate_option
"$1" "$2"
1406 validate_directory
"$1" "$worktree"
1410 validate_option
"$1" "$2"
1412 validate_directory
"$1" "$worktree"
1415 validate_option
"$1" "$2"
1417 validate_directory
"$1" "$tardir"
1421 validate_option
"$1" "$2"
1423 validate_directory
"$1" "$tardir"
1425 --architecture |
-a )
1426 validate_option
"$1" "$2"
1431 validate_option
"$1" "$2"
1436 validate_digit
"$1" "$jobs"
1441 validate_digit
'-j' "$jobs"
1445 validate_digit
'--jobs=' "$jobs"
1451 opt_incr_release
=opt_incr_release
1453 --skip-questions |
-S )
1454 opt_skipqsts
=opt_skipqsts
1457 verbose_level
=$
(( verbose_level
+ 1 ))
1460 # A trick for a second -v.
1463 --show-location |
-L )
1465 "QI_TARGETDIR=$QI_TARGETDIR" \
1466 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1467 "QI_WORKTREE=$QI_WORKTREE" \
1468 "QI_TARDIR=$QI_TARDIR" \
1469 "QI_OUTDIR=$QI_OUTDIR"
1472 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' | \
1477 --version |
--versio |
--versi |
--vers | \
1478 -version |
-versio |
-versi |
-vers |
-V | version
)
1479 echo "$PROGRAM version @VERSION@"
1483 _readstdin
=readstdin
1488 break; # End of options.
1491 warn
"qi: Unrecognized option: $1" \
1492 "Try '${PROGRAM} --help' for more information."
1496 break; # No more options.
1502 readconfig validate_mode validate_option validate_directory validate_digit
1504 # When there are no arguments, show the help
1512 # Program sanity check
1514 for need
in awk basename chmod cp dirname find fold graft
grep \
1515 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1517 if ! \
command -v $need > /dev
/null
1519 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1525 # Set verbosity level and flags
1527 if test "$verbose_level" -gt 0
1529 if test "$verbose_level" -eq 1
1537 # Read standard input if FILE is -, or when
1538 # FILE is not connected to a terminal
1540 if test "$_readstdin" = readstdin
1544 warn
"qi: I won't read from a connected terminal." \
1545 "Try '${PROGRAM} --help' for more information."
1549 # Unset positional parameters setting $# to zero
1552 # Assign remaining arguments to the positional parameters
1555 set -- "$@" "$input"
1562 warn
"qi: We need at least one (valid) command." \
1563 "Try '${PROGRAM} --help' for more information."
1567 # Validate 'packagedir' and 'targetdir' as canonical directories
1569 # The single slash '/' does not qualify here
1570 if ! fnmatch
'/?*' "$packagedir"
1572 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1575 if test ! -d "$packagedir"
1577 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1581 # The single slash '/' is valid here
1582 if ! fnmatch
'/*' "$targetdir"
1584 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1587 if test ! -d "$targetdir"
1589 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1593 # Validate 'rootdir' directory
1595 if test -n "$rootdir"
1597 if test -d "$rootdir" && test "$rootdir" != /
1599 rootdir
="${rootdir%/}" # Remove slash from the end.
1601 # A workaround for graft-2.13+. The specified directory is
1602 # relative to the log file, we prepend it inside 'rootdir'
1604 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1605 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1607 # Compose 'rootdir' and log file option to be used with graft(1)
1608 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1610 # Unset variables coming from eval
1611 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1613 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1620 # Ensure 'TMPDIR' creation to prefix temporary files
1622 if test ! -d "$TMPDIR"
1624 mkdir
-p -- "$TMPDIR" || chkstatus_or_exit
1626 readonly TMPDIR packagedir targetdir
1628 # Process each package or recipe provided on the command-line