2 # Copyright (C) 2016-2024 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
112 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
117 echo "${PROGRAM}: cannot read ${1}: Permission denied" 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 is_readable
"$HOME/.qirc" 2> /dev
/null
163 _rcfile
="$HOME/.qirc"
166 echo "Importing configuration file from \`${_rcfile}' ..."
167 .
"$_rcfile" || chkstatus_or_exit
5
172 # Complain if the file cannot be well-read
173 is_readable
"$1" ||
exit 4
175 # Complain if the file does not end at expected extension
176 if ! fnmatch
"*.${2}" "$1"
178 echo "${PROGRAM}: \`${1}' does not end in .${2}" 1>&2
182 # Complain if the file is not regular
185 echo "${PROGRAM}: \`${1}' is not a regular file." 1>&2
194 ( umask 022 ; mkdir
-p -- "$1" ||
exit $?
); chkstatus_or_exit
202 # Read configuration file if --no-rc is not passed
203 test "$opt_readrc" = opt_readrc
&& readconfig
208 echo "{@} Building \"${recipe}\" ..."
210 # A recipe is any valid regular file. Qi sets priorities for reading
211 # a recipe, the order in which qi looks for a recipe is:
213 # 1. Current working directory.
215 # 2. If the specified path name does not contain "recipe" as the last
216 # component. Qi will complete it by adding "recipe" to the path
219 # 3. If the recipe is not in the current working directory, it will be
220 # searched under '${worktree}/recipes'. The last component will be
221 # completed adding "recipe" to the specified path name.
223 if test ! -f "$recipe"
225 if test -e "${recipe}/recipe"
227 recipe
="${recipe}/recipe"
228 elif test -e "${worktree}/recipes/${recipe}/recipe"
230 recipe
="${worktree}/recipes/${recipe}/recipe"
234 test -f "$recipe" ||
{
235 warn
"\`${recipe}' is not a regular file."
239 # Complain if the file name is not "recipe"
241 if test "${recipe##*/}" != recipe
243 warn
"\`${recipe}' is not a valid recipe name."
247 # Start preparations to import the recipe
249 # Get working directory and base name of the recipe
251 CWD
="$(CDPATH='' cd -P -- "$
(dirname -- "$recipe")" && pwd -P)"
252 recipe
="$(basename -- "$recipe")"
254 # Check readability for load the recipe on success
256 is_readable
"${CWD}/$recipe" ||
exit 4
258 # Create external directories if needed
259 for directory
in "${worktree}/archive" "${worktree}/patches" "${worktree}/recipes" "$tardir"
261 make_directory
"$directory"
265 # Variables treatment for the current and the next recipe.
267 # Unset special variables that can only be predefined in
268 # the recipe and does not come from '${sysconfdir}/qirc'
271 srcdir destdir pkgname pkgversion pkgcategory program version release \
272 fetch description homepage license replace full_pkgname docs docsdir \
273 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
275 # The following variables must be restored, later
276 save_arch
="${save_arch:=$arch}"
277 save_jobs
="${save_jobs:=$jobs}"
278 save_outdir
="${save_outdir:=$outdir}"
279 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
280 save_tarlz_compression_options
="${save_tarlz_compression_options:=$tarlz_compression_options}"
282 # Reset variable values in case of return
285 outdir
="$save_outdir"
286 opt_nopkg
="$save_opt_nopkg"
287 tarlz_compression_options
="$save_tarlz_compression_options"
289 # The following variables cannot be redefined on a recipe
290 readonly worktree netget rsync
297 # Check if 'opt_skiprecipe' has been declared to ignore
298 # the current recipe and continue with the next recipe
299 if test "$opt_skiprecipe" = "opt_skiprecipe"
301 unset -v opt_skiprecipe
302 warn
"${recipe}: The variable 'opt_skiprecipe' has been used here."
306 # Check for required variables
307 if test -z "$program"
309 warn
"${recipe}: The variable 'program' is not defined."
312 if test -z "$version"
314 warn
"${recipe}: The variable 'version' is not defined."
319 warn
"${recipe}: The variable 'arch' is not defined."
322 if test -z "$release"
324 warn
"${recipe}: The variable 'release' is not defined."
328 # Pre-settings before to start building
330 # Increment the release number if the option was given
331 if test "$opt_incr_release" = opt_incr_release
333 release
=$
(( release
+ 1 ))
336 # Allow the dot as definition for 'tardir'
337 if test "$tardir" = .
342 # Set default values for the following special variables
344 pkgname
="${pkgname:=$program}"
345 pkgversion
="${pkgversion:=$version}"
346 srcdir
="${srcdir:=${program}-$version}"
347 srcdir
="${TMPDIR}/$srcdir"
348 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
350 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
351 if test -n "$pkgcategory"
353 pkgcategory
="@${pkgcategory}"
356 # Compose the full package name
357 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
359 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
360 outdir
="${outdir}/${arch}"
362 # If a package is going to be created the existence of a
363 # previous build will be detected and reported. Under normal
364 # conditions the recipe is built as long as it is newer than
365 # the produced package, if not, we warn to the user about it.
366 # Rebuilding the package is possible (through the force ;-)
368 if test "$opt_nopkg" != opt_nopkg
&& \
369 { test "$opt_force" != opt_force
&& \
370 test -e "${outdir}/${full_pkgname}.tlz" ; }
372 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
376 "This recipe is
more RECENT than the produced package
:" \
378 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
379 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
381 " This recipe will be processed ...
" \
383 elif test -e "${CWD}/post-install
" && \
384 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
388 "The post-install
script is
more RECENT than the recipe
:" \
390 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
391 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
393 " This recipe will be re-processed ...
" \
395 touch "${CWD}/$recipe"
396 elif test "$opt_skipqsts" = opt_skipqsts
398 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
403 "This recipe ALREADY produced a package
:" \
404 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
406 "The recipe is still OLDER than the produced package
:" \
407 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
409 " Probably nothing has changed.
" \
412 # In non-interactive mode the user is asked about
413 # rebuilding the package; on interactive mode,
414 # the user need to pass the option explicitly.
418 warn "Use the
--force option to reprocess
${CWD}/${recipe}.
" ""
423 "Do you want to rebuild this package? ...
" \
424 "(1) Yes
, built it
" \
425 "(2) No
, skip it.
[Default
]" \
426 "(3) Resume
(skipping completed recipes
)" \
427 "Enter an option number
:" > /dev/tty
428 IFS= read -r ANSWER < /dev/tty || exit 2;
431 echo "$ANSWER" > /dev/tty
436 echo "=== Building unprocessed or recently modified recipe
(s
) ...
" > /dev/tty
438 opt_skipqsts=opt_skipqsts
439 readonly opt_skipqsts
444 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
452 # Fetch remote sources
454 echo "=== Fetching remote sources
if needed ...
"
459 echo "=== Looking
for $origin ...
"
461 _source="${origin##*/}"; # Get the file name.
463 echo "=== Verifying checksum
file found \
`${tardir}/${_source}.sha256'"
464 if test -e "${tardir}/${_source}.sha256"
466 ( cd -- "$tardir" && sha256sum -c "${_source}.sha256" )
471 warn " Checksum file \`${_source}.sha256
' does not exist"
473 # Download source or resume, if allowed
475 if test ! -e "${tardir}/$_source"
477 warn "=== Attempting to get it from $origin ..."
483 cd -- "$tardir" && $rsync "$origin" || exit $?
484 sha256sum "$_source" > "${_source}.sha256"
485 ); chkstatus_or_exit 10
489 cd -- "$tardir" && $netget "$origin" || exit $?
490 sha256sum "$_source" > "${_source}.sha256"
491 ); chkstatus_or_exit 10
494 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
498 unset -v origin _source
500 warn "${recipe}: The variable 'fetch
' is empty."
503 # Prepare special directories for build the source,
504 # the destination and the output of the package
506 echo "=== Preparing directories ..."
508 if test -z "$keep_srcdir"
512 rm -rf -- "$srcdir" || chkstatus_or_exit
513 echo "removed directory: '$srcdir'"
516 warn "WARNING: The variable 'keep_srcdir
' has been set (${keep_srcdir})."
519 if test -z "$keep_destdir"
521 if test -e "$destdir"
523 rm -r -- "$destdir" || chkstatus_or_exit
524 echo "removed directory: '$destdir'"
526 make_directory "$destdir"
528 warn "WARNING: The variable 'keep_destdir
' has been set (${keep_destdir})."
531 make_directory "$outdir"
533 echo "=== Changing to '${TMPDIR}' ..."
534 cd -- "$TMPDIR" || chkstatus_or_exit
536 # Set trap before to run the build() function in order
537 # to catch the return status, exit code 2 if fails
539 trap 'chkstatus_or_exit
2' EXIT HUP INT QUIT ABRT TERM
541 # Determine if the debugging indicators of the shell should be
542 # retained, assuming that it has been previously passed
544 _xtrace_flag=_xtrace_flag_is_set ;;
547 echo "=== Running the 'build
' function ..."
551 # Check recipe to run (extra) defined functions by the packager
553 $(awk '!/^build
[ (]/ && /^
[^
{}]+ *\
(\
)/{ gsub
(/[()]/, "", $1); print
$1 }' "${CWD}/$recipe")
555 # Check if it is a shell function
556 case $(LC_ALL=C type $_definition) in
558 # Call and undo the function after executing it
560 unset -f $_definition
566 # Turn off possible shell flags coming from the recipe
569 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
574 # Reset given signals
575 trap - EXIT HUP INT QUIT ABRT TERM
577 # If 'destdir
' is empty, the package won't be created
578 if rmdir -- "$destdir" 2> /dev
/null
580 warn
"The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
584 # Create (make) the package
586 if test "$opt_nopkg" != opt_nopkg
588 # Edit the recipe when 'release' is incremented
589 if test "$opt_incr_release" = opt_incr_release
591 echo ",s/^\\(release\\)=.*/\\1=${release}/"$
'\nw' | \
592 ed
"${CWD}/$recipe" || chkstatus_or_exit
595 make_directory
"${destdir}/var/lib/qi"
597 # Include a copy of the recipe into the package
598 cp -p "${CWD}/$recipe" \
599 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
600 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
603 # Produce a checksum file for recipe copy
604 cd -- "${destdir}/var/lib/qi" && \
605 sha256sum
"${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
606 ); chkstatus_or_exit
4
608 # Detect post-install script for inclusion
610 if test -f "${CWD}/post-install"
612 echo "${CWD}/post-install: Detected."
613 cp -p "${CWD}/post-install" \
614 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
615 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
619 # Detect declared package name(s) for later replacement
621 if test -n "$replace"
624 "=== The following package names has been declared for replacement:" \
627 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
630 printf '%s\n' "$replace" >> \
631 "${destdir}/var/lib/qi/${full_pkgname}.replace"
636 # Create (external) meta file for package information,
637 # make a copy of it for the package database
638 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
639 do_meta
> "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
640 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
641 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
643 # Produce the package
644 cd -- "$destdir" && mode_create
"${outdir}/${full_pkgname}.tlz"
647 # Back to the current working directory
648 cd -- "$CWD" || chkstatus_or_exit
650 echo "=== Deleting 'srcdir' or 'destdir' ..."
651 if test "$opt_keep" != opt_keep
653 if test -z "$keep_srcdir"
657 rm -rf -- "$srcdir" || chkstatus_or_exit
658 echo "removed directory: '$srcdir'"
661 if test -z "$keep_destdir"
663 if test -e "$destdir"
665 rm -rf -- "$destdir" || chkstatus_or_exit
666 echo "removed directory: '$destdir'"
671 " The following directories will be preserved:" \
675 "The '--keep' option has been used."
678 # Install or upgrade the package after build
679 if test "$opt_nopkg" != opt_nopkg
681 if test "$opt_install" = opt_install
683 mode_install
"${outdir}/${full_pkgname}.tlz"
684 elif test "$opt_upgrade" = opt_upgrade
686 mode_upgrade
"${outdir}/${full_pkgname}.tlz"
690 warn
"{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
695 directory
="$(dirname -- "$1")"
697 # Perform sanity checks
699 if ! fnmatch
'/?*' "$directory"
701 warn
"${PROGRAM}: Output directory \`${directory}' is not fully qualified"
704 is_readable
"$directory" ||
exit 4
706 name
="$(basename -- "$1")"
709 echo "{#} Creating package name \`${name}' ..."
711 if test "$name" = "${name%.tlz}"
713 warn
"Package format '$name' not supported." \
714 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
718 # If needed, assign default values for compression options
719 tarlz_compression_options
="${tarlz_compression_options:=-9 --solid}"
721 # Pass extra options to tarlz(1)
722 if test -n "$SOURCE_DATE_EPOCH"
724 tarlz_compression_options
="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
727 ( umask 022 ; tarlz
$tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
730 ( cd -- "$directory" && sha256sum
"$name" > "${name}.sha256" )
733 warn
"{#} Package \"${name}\" created on ${directory}." ""
735 # Remove used variables
736 unset -v directory name
741 # Read configuration file if --no-rc is not passed
742 test "$opt_readrc" = opt_readrc
&& readconfig
744 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
747 echo "{<} Removing \`$rootdir${expunge}' ..."
749 # Complain if the package directory cannot be well-read
751 is_readable
"$rootdir${expunge}" ||
exit 4
753 # Validate package directory as such
755 test -d "$rootdir${expunge}" ||
{
756 warn
"Package '$rootdir${expunge}' is not a valid directory."
760 # Remove package from Graft control
762 # Scan for possible conflicts, stop if arise
763 if test "$opt_prune" != opt_prune
765 echo "=== Checking for possible conflicts ..."
766 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
770 " A conflict occurred during uninstallation;" \
771 "Unless the --prune option is given, this package will be PRESERVED."
776 # Remove objects (files, links or directories) from the target
777 # directory that are in conflict with the package directory
779 echo "=== Pruning any conflict ..."
780 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
783 echo "=== Disabling links ..."
784 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
787 # Delete package directory
788 if test "$opt_keep" != opt_keep
790 echo "=== Deleting package directory ..."
791 if is_readable
"${rootdir}$expunge"
793 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
794 echo "removed directory: '${rootdir}$expunge'"
798 warn
"{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
804 validate_file
"$1" tlz
806 # Read configuration file if --no-rc is not passed
807 test "$opt_readrc" = opt_readrc
&& readconfig
809 name
="$(basename -- "$1" .tlz)"
812 echo "{>} Installing package \`${name}.tlz' ..."
814 echo "=== Checking tarball integrity ..."
815 tarlz
--missing-crc -tf "$1" > /dev
/null
818 # To accept random directory from the upgrade mode
820 _packagedir
="${_packagedir:=$packagedir}"
822 make_directory
"$rootdir${_packagedir}/$name"
824 # Scan for possible conflicts, stop if arise
825 if test "$opt_prune" != opt_prune
827 echo "=== Checking for possible conflicts ..."
828 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
832 " A conflict occurred during installation;" \
833 "Unless the --prune option is given, this package won't be LINKED."
838 echo "=== Decompressing package ..."
839 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
842 # Transite package to Graft control
844 # Remove objects (files, links or directories) from the target
845 # directory that are in conflict with the package directory
846 echo "=== Pruning any conflict ..."
847 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
850 echo "=== Enabling symbolic links ..."
851 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
854 # Avoid unnecessary runs coming from the upgrade mode,
855 # this is when the incoming package is **pre-installed**
857 if test "$_isUpgrade" != _isUpgrade.on
859 # Show package description
860 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
862 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
863 elif test -r "${1}.txt"
865 # From external meta file (current directory)
866 awk '/^#/' "${1}.txt"
868 warn
"Description file not found for '$name'."
871 # Check and run the post-install script if exist
872 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
874 echo "=== Running post-install script for \`${name}' ..."
876 # Rely on 'targetdir' if 'rootdir' is empty
877 cd -- "${rootdir:=$targetdir}"/ && \
878 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
882 # Check if there are declared packages for replacement
883 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
887 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
889 if test ! -e "$replace"
891 warn
" Declared package \`${replace}' to be replaced does not exist. [Ignored]"
895 replace
="${replace##*/}"
897 # The search for the package to be replaced cannot
898 # be the same to the incoming package, even to the
899 # temporary location coming from the upgrade mode
900 if test "$replace" = "$name" || \
901 test "_x_${replace}" = "_x_${PRVLOC##*/}"
906 warn
"WARNING: Replacing package \`${replace}' ..."
908 # Since the links belongs to the new package, only
909 # those which are not in conflict can be deleted.
910 # To complete, we will remove the package directory
912 graft
-d -D -u $graft_r \
913 -t "$targetdir" "$replace" > /dev
/null
2>&1
915 rm -rf -- "$rootdir${_packagedir}/$replace"
917 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
921 warn
"{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
922 unset -v name _packagedir
; # Remove used variables.
927 validate_file
"$1" order
929 # Get a clean list of the file while printing its contents in
930 # reverse order. Last awk(1) in the pipeline eliminates
931 # non-consecutive lines: duplicates, blank lines, and colons.
932 # Comment lines beginning with '#' are allowed; parentheses
936 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
937 for( i=NF; i > 0; i-- ) print $i
938 }' "$1" |
awk '!s[$0]++'
943 validate_file
"$1" tlz
945 # Read configuration file if --no-rc is not passed
946 test "$opt_readrc" = opt_readrc
&& readconfig
948 incoming
="$(basename -- "$1" .tlz)"
951 echo "{^} Upgrading to package \`${incoming}.tlz' ..."
953 # Check package pre-existence
954 if test "$opt_force" != opt_force
&& \
955 test -e "$rootdir${packagedir}/$incoming"
958 " The package to be upgraded already exist;" \
959 "Unless the --force option is given, this package won't be UPGRADED."
963 # Ignore some signals until the upgrade process is complete
964 trap "" HUP INT QUIT ABRT TERM
966 # Check blacklisted packages before to proceed with the upgrade
968 echo "=== Checking blacklist ..."
969 for item
in $blacklist
974 " A blacklisted package name has been detected:" \
977 "^ This package will be INSTALLED instead of being upgraded ..."
978 opt_prune
=opt_prune mode_install
"$1"
985 # Prepare the package to install it in a temporary but custom location
987 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
988 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
990 echo "=== Pre-installing package in temporary location ..."
992 opt_prune
=opt_prune
; # Turns on prune operation.
994 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
995 _isUpgrade
=_isUpgrade.off
997 echo "=== Looking for installations under the same name ..."
998 for previous_package
in "$rootdir${packagedir}/${incoming%%_*}"_
*
1000 test -e "$previous_package" ||
continue
1002 # Previous package directory to be removed cannot be
1003 # the same to the temporary (private) location
1004 if test "$previous_package" = "$PRVLOC"
1009 # The directory of the package could be preserved if --keep
1010 mode_remove
"$previous_package"
1012 unset -v previous_package
1014 # Re-install incoming package to the final location
1017 opt_prune
=opt_prune.off
; # Turns off prune operation.
1019 echo "=== Deleting temporary location ..."
1020 if is_readable
"$PRVLOC"
1022 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1023 echo "removed directory: '$PRVLOC'"
1027 warn
"{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1030 # Reset given signals
1031 trap - HUP INT QUIT ABRT TERM
1036 validate_file
"$1" tlz
1039 echo "{!} Warning about the content of \`$(basename -- "$1")' ..."
1041 # List content of files excluding directories
1042 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1048 validate_file
"$1" tlz
1050 name
="$(basename -- "$1" .tlz)"
1053 echo "{-} Extracting from package \`${name}.tlz' ..."
1055 # Set random directory under 'TMPDIR' using 'name'
1056 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1058 # Trap to remove 'PRVDIR' on disruptions
1059 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1061 # Create 'PRVDIR' removing access for all but user
1062 ( umask 077 ; mkdir
-- "$PRVDIR" )
1063 mkdir
-p -- "$PRVDIR"
1064 chmod 700 -- "$PRVDIR"
1066 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1069 # Try to remove (empty) 'PRVDIR' on failure
1074 warn
"" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1076 # Remove used variables
1077 unset -v name PRVDIR
1079 # Reset given signals
1080 trap - HUP INT ABRT TERM
1083 ### Extra functions to be used during the modes
1091 tar -tf "$file" > /dev
/null
&& tar -xpf "$file"
1094 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1095 gzip -cd "$file" |
tar -tf - > /dev
/null
&& gzip -cd "$file" |
tar -xpf -
1098 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1099 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& bzip2 -cd "$file" |
tar -xpf -
1103 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& lzip
-cd "$file" |
tar -xpf -
1107 xz
-cd "$file" |
tar -tf - > /dev
/null
&& xz
-cd "$file" |
tar -xpf -
1110 *.
tar.zst |
*.tzst
)
1111 zstd
-cd "$file" |
tar -tf - > /dev
/null
&& zstd
-cd "$file" |
tar -xpf -
1115 unzip -t "$file" > /dev
/null
&& unzip "$file" > /dev
/null
1119 gzip -t "$file" && gzip -cd "$file" > "$(basename -- "$file" .gz)"
1123 gzip -t "$file" && gzip -cd "$file" > "$(basename -- "$file" .Z)"
1127 bzip2 -t "$file" && bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1131 lzip
-t "$file" && lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1135 xz
-t "$file" && xz
-cd "$file" > "$(basename -- "$file" .xz)"
1139 zstd
-qt "$file" && zstd
-cd "$file" > "$(basename -- "$file" .zst)"
1143 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1152 # Extract information from the recipe to create the meta file.
1154 # The package description is pre-formatted in 78 columns,
1155 # the '#' character and a space is added as prefix to conform
1156 # the 80 columns in total
1157 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1159 # Include build flags only if it is a real architecture
1160 if test "$arch" != noarch
1164 QICFLAGS=\"$QICFLAGS\"
1165 QICXXFLAGS=\"$QICXXFLAGS\"
1166 QILDFLAGS=\"$QILDFLAGS\"
1167 QICPPFLAGS=\"$QICPPFLAGS\""
1170 # Print saving the rest of the package information
1174 pkgversion=$pkgversion
1177 pkgcategory=\"${pkgcategory#@*}\"
1178 full_pkgname=$full_pkgname
1179 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1180 homepage=\"$homepage\"
1181 license=\"$license\"
1183 replace=\"$replace\"
1189 packagedir
=@PACKAGEDIR@
1190 targetdir
=@TARGETDIR@
1191 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1192 _rcfile
=@SYSCONFDIR@
/qirc
1193 opt_readrc
=opt_readrc
1194 opt_install
=opt_install.off
1195 opt_upgrade
=opt_upgrade.off
1196 opt_force
=opt_force.off
1197 opt_keep
=opt_keep.off
1198 opt_incr_release
=opt_incr_release.off
1199 opt_skipqsts
=opt_skipqsts.off
1200 opt_nopkg
=opt_nopkg.off
1201 opt_prune
=opt_prune.off
1212 _isUpgrade
=_isUpgrade.off
1215 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1216 QICFLAGS
="${QICFLAGS:-@QICFLAGS@}"
1217 QICXXFLAGS
="${QICXXFLAGS:-@QICXXFLAGS@}"
1218 QILDFLAGS
="${QILDFLAGS:-@QILDFLAGS@}"
1219 QICPPFLAGS
="${QICPPFLAGS:-@QICPPFLAGS@}"
1220 worktree
=/usr
/src
/qi
1221 tardir
=${worktree}/sources
1223 netget
="wget2 -c -w1 -t3 --no-check-certificate"
1224 rsync
="rsync -v -a -L -z -i --progress"
1225 tarlz_compression_options
="-9 --solid"
1226 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1231 ### Parse commands and options
1237 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1238 "Switching to another command is not allowed (${1})."
1247 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1248 "Try '${PROGRAM} --help' for more information."
1253 validate_directory
()
1257 warn
"${PROGRAM}: Value \"${2}\" from '${1%%=*}' option must be a valid directory name"
1267 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1270 warn
"${PROGRAM}: The '${name}' option has no defined value"
1274 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1278 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1294 validate_mode
install
1298 validate_mode remove
1302 validate_mode upgrade
1306 validate_mode extract
1310 validate_mode create
1322 opt_readrc
=opt_readrc.off
1325 opt_install
=opt_install
1328 opt_upgrade
=opt_upgrade
1340 validate_option
"$1" "$2"
1342 validate_directory
"$1" "$packagedir"
1346 validate_option
"$1" "$2"
1347 packagedir
="${1#*=}"
1348 validate_directory
"$1" "$packagedir"
1351 validate_option
"$1" "$2"
1353 validate_directory
"$1" "$targetdir"
1357 validate_option
"$1" "$2"
1359 validate_directory
"$1" "$targetdir"
1362 validate_option
"$1" "$2"
1364 validate_directory
"$1" "$rootdir"
1368 validate_option
"$1" "$2"
1370 validate_directory
"$1" "$rootdir"
1373 validate_option
"$1" "$2"
1375 validate_directory
"$1" "$outdir"
1379 validate_option
"$1" "$2"
1381 validate_directory
"$1" "$outdir"
1384 validate_option
"$1" "$2"
1386 validate_directory
"$1" "$worktree"
1390 validate_option
"$1" "$2"
1392 validate_directory
"$1" "$worktree"
1395 validate_option
"$1" "$2"
1397 validate_directory
"$1" "$tardir"
1401 validate_option
"$1" "$2"
1403 validate_directory
"$1" "$tardir"
1405 --architecture |
-a )
1406 validate_option
"$1" "$2"
1411 validate_option
"$1" "$2"
1416 validate_digit
"$1" "$jobs"
1421 validate_digit
'-j' "$jobs"
1425 validate_digit
'--jobs=' "$jobs"
1431 opt_incr_release
=opt_incr_release
1433 --skip-questions |
-S )
1434 opt_skipqsts
=opt_skipqsts
1437 verbose_level
=$
(( verbose_level
+ 1 ))
1440 # A trick for a second -v.
1443 --show-location |
-L )
1444 test "$opt_readrc" = opt_readrc
&& readconfig
1446 "QI_TARGETDIR=$targetdir" \
1447 "QI_PACKAGEDIR=$packagedir" \
1448 "QI_WORKTREE=$worktree" \
1449 "QI_TARDIR=$tardir" \
1453 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' |
help )
1457 --version |
--versio |
--versi |
--vers | \
1458 -version |
-versio |
-versi |
-vers |
-V | version
)
1459 echo "$PROGRAM version @VERSION@"
1463 _readstdin
=readstdin
1468 break; # End of options.
1471 warn
"qi: Unrecognized option: $1" \
1472 "Try '${PROGRAM} --help' for more information."
1476 break; # No more options.
1481 unset -f validate_mode validate_option validate_directory validate_digit
1483 # When there are no arguments, show the help
1491 # Program sanity check
1493 for need
in awk basename chmod cp dirname find fold graft
grep \
1494 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1496 if ! \
command -v $need > /dev
/null
1498 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1504 # Set verbosity level and flags
1506 if test "$verbose_level" -gt 0
1508 if test "$verbose_level" -eq 1
1516 # Read standard input if FILE is -, or when
1517 # FILE is not connected to a terminal
1519 if test "$_readstdin" = readstdin
1523 warn
"qi: I won't read from a connected terminal." \
1524 "Try '${PROGRAM} --help' for more information."
1528 # Unset positional parameters setting $# to zero
1531 # Assign remaining arguments to the positional parameters
1534 set -- "$@" "$input"
1541 warn
"qi: We need at least one (valid) command." \
1542 "Try '${PROGRAM} --help' for more information."
1546 # Validate 'packagedir' and 'targetdir' as canonical directories
1548 # The single slash '/' does not qualify here
1549 if ! fnmatch
'/?*' "$packagedir"
1551 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1554 if test ! -d "$packagedir"
1556 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1560 # The single slash '/' is valid here
1561 if ! fnmatch
'/*' "$targetdir"
1563 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1566 if test ! -d "$targetdir"
1568 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1572 # Validate 'rootdir' directory
1574 if test -n "$rootdir"
1576 if test -d "$rootdir" && test "$rootdir" != /
1578 rootdir
="${rootdir%/}" # Remove slash from the end.
1580 # A workaround for graft-2.13+. The specified directory is
1581 # relative to the log file, we prepend it inside 'rootdir'
1583 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1584 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1586 # Compose 'rootdir' and log file option to be used with graft(1)
1587 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1589 # Unset variables coming from eval
1590 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1592 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1599 # Ensure 'TMPDIR' creation to prefix temporary files
1600 make_directory
"$TMPDIR"
1602 # Do not allow the following variables to be modified
1603 readonly TMPDIR packagedir targetdir opt_readrc
1605 # Process each package or recipe provided on the command-line.