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 # Create external directories if needed
232 for directory
in "${worktree}/archive" "${worktree}/patches" "${worktree}/recipes" "$tardir"
234 test -d "$directory" || mkdir
-p -- "$directory"
238 # Variables treatment for the current and the next recipe.
240 # Unset special variables that can only be predefined in
241 # the recipe and does not come from '${sysconfdir}/qirc'
244 srcdir destdir pkgname pkgversion pkgcategory program version release \
245 fetch description homepage license replace full_pkgname docs docsdir \
246 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
248 # The following variables must be restored, later
249 save_arch
="${save_arch:=$arch}"
250 save_jobs
="${save_jobs:=$jobs}"
251 save_outdir
="${save_outdir:=$outdir}"
252 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
253 save_tarlz_compression_options
="${save_tarlz_compression_options:=$tarlz_compression_options}"
255 # Reset variable values in case of return
258 outdir
="$save_outdir"
259 opt_nopkg
="$save_opt_nopkg"
260 tarlz_compression_options
="$save_tarlz_compression_options"
262 # The following variables cannot be redefined on a recipe
263 readonly worktree netget rsync
270 # Check if 'opt_skiprecipe' has been declared to ignore
271 # the current recipe and continue with the next recipe
272 if test "$opt_skiprecipe" = "opt_skiprecipe"
274 unset -v opt_skiprecipe
275 warn
"${recipe}: The variable 'opt_skiprecipe' has been used here."
279 # Check for required variables
280 if test -z "$program"
282 warn
"${recipe}: The variable 'program' is not defined."
285 if test -z "$version"
287 warn
"${recipe}: The variable 'version' is not defined."
292 warn
"${recipe}: The variable 'arch' is not defined."
295 if test -z "$release"
297 warn
"${recipe}: The variable 'release' is not defined."
301 # Pre-settings before to start building
303 # Increment the release number if the option was given
304 if test "$opt_incr_release" = opt_incr_release
306 release
=$
(( release
+ 1 ))
309 # Allow the dot as definition for 'tardir'
310 if test "$tardir" = .
315 # Set default values for the following special variables
317 pkgname
="${pkgname:=$program}"
318 pkgversion
="${pkgversion:=$version}"
319 srcdir
="${srcdir:=${program}-$version}"
320 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
322 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
323 if test -n "$pkgcategory"
325 pkgcategory
="@${pkgcategory}"
328 # Compose the full package name
329 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
331 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
332 outdir
="${outdir}/${arch}"
334 # If a package is going to be created the existence of a
335 # previous build will be detected and reported. Under normal
336 # conditions the recipe is built as long as it is newer than
337 # the produced package, if not, we warn to the user about it.
338 # Rebuilding the package is possible (through the force ;-)
340 if test "$opt_nopkg" != opt_nopkg
&& \
341 { test "$opt_force" != opt_force
&& \
342 test -e "${outdir}/${full_pkgname}.tlz" ; }
344 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
348 "This recipe is
more RECENT than the produced package
:" \
350 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
351 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
353 " This recipe will be processed ...
" \
355 elif test -e "${CWD}/post-install
" && \
356 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
360 "The post-install
script is
more RECENT than the recipe
:" \
362 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
363 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
365 " This recipe will be re-processed ...
" \
367 touch "${CWD}/$recipe"
368 elif test "$opt_skipqsts" = opt_skipqsts
370 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
375 "This recipe ALREADY produced a package
:" \
376 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
378 "The recipe is still OLDER than the produced package
:" \
379 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
381 " Probably nothing has changed.
" \
384 # In non-interactive mode, the user is asked about
385 # rebuilding the package. In interactive mode,
386 # the user need to pass the option explicitly
390 "Do you want to rebuild this package?
" \
392 "2) No
, skip it
[default
]" \
393 "3) Resume
, skipping completed recipes
" \
394 "Enter an option number
:" > /dev/tty
395 IFS= read -r ANSWER < /dev/tty || exit 2;
398 echo "$ANSWER" > /dev/tty
403 echo "=== Building unprocessed or recently modified recipe
(s
) ...
" > /dev/tty
405 opt_skipqsts=opt_skipqsts
406 readonly opt_skipqsts
411 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
417 warn "Use the
--force option to reprocess
${CWD}/${recipe}.
" ""
423 # Fetch remote sources
425 echo "=== Fetching remote sources
if needed ...
"
430 echo "=== Looking
for $origin ...
"
432 _source="${origin##*/}"; # Get the file name.
434 echo "=== Verifying checksum
file found \
`${tardir}/${_source}.sha256'"
435 if test -e "${tardir}/${_source}.sha256"
437 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
441 warn " Checksum file \`${_source}.sha256
' does not exist"
444 # Download source or resume if allowed
446 if test ! -e "${tardir}/$_source"
448 warn "=== Attempting to get it from $origin ..."
454 cd -- "$tardir" && $rsync "$origin" || exit $?
455 sha256sum "$_source" > "${_source}.sha256"
456 ); chkstatus_or_exit 10
460 cd -- "$tardir" && $netget "$origin" || exit $?
461 sha256sum "$_source" > "${_source}.sha256"
462 ); chkstatus_or_exit 10
465 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
469 unset -v origin _source
471 warn "${recipe}: The variable 'fetch
' is empty."
474 # Prepare special directories for build the source,
475 # the destination and the output of the package
477 echo "=== Preparing directories ..."
479 if test -z "$keep_srcdir"
481 if test -e "${TMPDIR}/$srcdir"
483 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
484 echo "removed directory: '${TMPDIR}/$srcdir'"
488 "WARNING: The variable 'keep_srcdir
' has been set (${keep_srcdir})."
491 if test -z "$keep_destdir"
493 if test -e "$destdir"
495 rm -r -- "$destdir" || chkstatus_or_exit
496 echo "removed directory: '$destdir'"
498 mkdir -p -- "$destdir" || chkstatus_or_exit
499 echo "mkdir: created directory '$destdir'"
502 if test ! -e "$outdir"
504 mkdir -p -- "$outdir" || chkstatus_or_exit
505 echo "mkdir: created directory '$outdir'"
508 echo "=== Changing to '${TMPDIR}' ..."
509 cd -- "$TMPDIR" || chkstatus_or_exit
511 # Set trap before to run the build() function in order
512 # to catch the return status, exit code 2 if fails
514 trap 'chkstatus_or_exit
2' EXIT HUP INT QUIT ABRT TERM
516 # Determine if the debugging indicators of the shell should be
517 # retained, assuming that it has been previously passed
519 _xtrace_flag=_xtrace_flag_is_set ;;
522 echo "=== Running the 'build
' function ..."
526 # Check recipe to run (extra) defined functions by the packager
528 $(awk '!/^build
[ (]/ && /^
[^
{}]+ *\
(\
)/{ gsub
(/[()]/, "", $1); print
$1 }' "${CWD}/$recipe")
530 # Check if it is a shell function
531 case $(LC_ALL=C type $_definition) in
533 # Call and undo the function after executing it
535 unset -f $_definition
541 # Turn off possible shell flags coming from the recipe
544 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
549 # Reset given signals
550 trap - EXIT HUP INT QUIT ABRT TERM
552 # If 'destdir
' is empty, the package won't be created
553 if rmdir -- "$destdir" 2> /dev
/null
555 warn
"The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
559 # Create (make) the package
561 if test "$opt_nopkg" != opt_nopkg
563 # Edit the recipe when 'release' is incremented
564 if test "$opt_incr_release" = opt_incr_release
566 echo ",s/^\\(release\\)=.*/\\1=${release}/"$
'\nw' | \
567 ed
"${CWD}/$recipe" || chkstatus_or_exit
570 mkdir
-p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
572 # Include a copy of the recipe into the package
573 cp -p "${CWD}/$recipe" \
574 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
575 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
578 # Produce a checksum file for recipe copy
579 cd -- "${destdir}/var/lib/qi" && \
580 sha256sum
"${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
581 ); chkstatus_or_exit
4
583 # Detect post-install script for inclusion
585 if test -f "${CWD}/post-install"
587 echo "${CWD}/post-install: Detected."
588 cp -p "${CWD}/post-install" \
589 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
590 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
594 # Detect declared package name(s) for later replacement
596 if test -n "$replace"
599 "=== The following package names has been declared for replacement:" \
602 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
605 printf '%s\n' "$replace" >> \
606 "${destdir}/var/lib/qi/${full_pkgname}.replace"
611 # Create (external) meta file for package information,
612 # make a copy of it for the package database
613 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
614 do_meta
> "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
615 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
616 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
618 # Produce the package
619 cd -- "$destdir" && mode_create
"${outdir}/${full_pkgname}.tlz"
622 # Back to the current working directory
623 cd -- "$CWD" || chkstatus_or_exit
625 echo "=== Deleting 'srcdir' or 'destdir' ..."
626 if test "$opt_keep" != opt_keep
628 if test -z "$keep_srcdir"
630 srcdir
="${srcdir%%/*}"; # Directory name without parents.
631 if test -e "${TMPDIR}/$srcdir"
633 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
634 echo "removed directory: '${TMPDIR}/$srcdir'"
637 if test -z "$keep_destdir"
639 if test -e "$destdir"
641 rm -rf -- "$destdir" || chkstatus_or_exit
642 echo "removed directory: '$destdir'"
647 " The following directories will be preserved:" \
648 "${TMPDIR}/$srcdir" \
651 "The '--keep' option has been used."
654 # Install or upgrade the package after build
655 if test "$opt_nopkg" != opt_nopkg
657 if test "$opt_install" = opt_install
659 mode_install
"${outdir}/${full_pkgname}.tlz"
660 elif test "$opt_upgrade" = opt_upgrade
662 mode_upgrade
"${outdir}/${full_pkgname}.tlz"
666 warn
"{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
671 directory
="$(dirname -- "$1")"
673 # Perform sanity checks
675 if ! fnmatch
'/?*' "$directory"
677 warn
"${PROGRAM}: Output directory \`${directory}' is not fully qualified"
680 is_readable
"$directory" ||
exit 4
682 name
="$(basename -- "$1")"
685 echo "{#} Creating package name \`${name}' ..."
687 if test "$name" = "${name%.tlz}"
689 warn
"Package format '$name' not supported." \
690 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
694 # If needed, assign default values for compression options
695 tarlz_compression_options
="${tarlz_compression_options:=-9 --solid}"
697 # Pass extra options to tarlz(1)
698 if test -n "$SOURCE_DATE_EPOCH"
700 tarlz_compression_options
="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
703 ( umask 022 ; tarlz
$tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
706 ( cd -- "$directory" && sha256sum
"$name" > "${name}.sha256" )
709 warn
"{#} Package \"${name}\" created on ${directory}." ""
711 # Remove used variables
712 unset -v directory name
717 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
720 echo "{<} Removing \`$rootdir${expunge}' ..."
722 # Complain if the package directory cannot be well-read
724 is_readable
"$rootdir${expunge}" ||
exit 4
726 # Validate package directory as such
728 test -d "$rootdir${expunge}" ||
{
729 warn
"Package '$rootdir${expunge}' is not a valid directory."
733 # Remove package from Graft control
735 # Scan for possible conflicts, stop if arise
736 if test "$opt_prune" != opt_prune
738 echo "=== Checking for possible conflicts ..."
739 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
743 " A conflict occurred during uninstallation;" \
744 "Unless the --prune option is given, this package will be PRESERVED."
749 # Remove objects (files, links or directories) from the target
750 # directory that are in conflict with the package directory
752 echo "=== Pruning any conflict ..."
753 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
756 echo "=== Disabling links ..."
757 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
760 # Delete package directory
761 if test "$opt_keep" != opt_keep
763 echo "=== Deleting package directory ..."
764 if is_readable
"${rootdir}$expunge"
766 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
767 echo "removed directory: '${rootdir}$expunge'"
771 warn
"{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
777 # Complain if the package cannot be well-read
779 is_readable
"$1" ||
exit 4
781 # Complain if the package does not end in .tlz
783 if ! fnmatch
'*.tlz' "$1"
785 warn
"\`${1}' does not end in .tlz"
789 # Make preparations to install the package
792 echo "{>} Installing $1 ..."
794 echo "=== Checking tarball integrity ..."
795 tarlz
--missing-crc -tf "$1" > /dev
/null
798 # To accept random directory from the upgrade mode
800 _packagedir
="${_packagedir:=$packagedir}"
802 # Create package directory using 'name'
804 name
="$(basename -- "$1" .tlz)"; # Get the file name.
806 if ! test -d "$rootdir${_packagedir}/$name"
808 mkdir
-p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
809 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
812 # Scan for possible conflicts, stop if arise
813 if test "$opt_prune" != opt_prune
815 echo "=== Checking for possible conflicts ..."
816 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
820 " A conflict occurred during installation;" \
821 "Unless the --prune option is given, this package won't be LINKED."
826 echo "=== Decompressing package ..."
827 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
830 # Transite package to Graft control
832 # Remove objects (files, links or directories) from the target
833 # directory that are in conflict with the package directory
834 echo "=== Pruning any conflict ..."
835 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
838 echo "=== Enabling symbolic links ..."
839 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
842 # Avoid unnecessary runs coming from the upgrade mode,
843 # this is when the incoming package is **pre-installed**
845 if test "$_isUpgrade" != _isUpgrade.on
847 # Show package description
848 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
850 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
851 elif test -r "${1}.txt"
853 # From external meta file (current directory)
854 awk '/^#/' "${1}.txt"
856 warn
"Description file not found for '$name'."
859 # Check and run the post-install script if exist
860 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
862 echo "=== Running post-install script for \`${name}' ..."
864 # Rely on 'targetdir' if 'rootdir' is empty
865 cd -- "${rootdir:=$targetdir}"/ && \
866 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
870 # Check if there are declared packages for replacement
871 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
875 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
877 if ! test -e "$replace"
879 warn
" Declared package \`${replace}' to be replaced does not exist. [Ignored]"
883 replace
="${replace##*/}"
885 # The search for the package to be replaced cannot
886 # be the same to the incoming package, even to the
887 # temporary location coming from the upgrade mode
888 if test "$replace" = "$name" || \
889 test "_x_${replace}" = "_x_${PRVLOC##*/}"
894 warn
"WARNING: Replacing package \`${replace}' ..."
896 # Since the links belongs to the new package, only
897 # those which are not in conflict can be deleted.
898 # To complete, we will remove the package directory
900 graft
-d -D -u $graft_r \
901 -t "$targetdir" "$replace" > /dev
/null
2>&1
903 rm -rf -- "$rootdir${_packagedir}/$replace"
905 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
909 warn
"{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
910 unset -v name _packagedir
; # Remove used variables.
915 # Complain if the file cannot be well-read
917 is_readable
"$1" ||
exit 4
919 # Complain if the file does not end in .order
921 if ! fnmatch
'*.order' "$1"
923 warn
"\`${1}' does not end in .order"
927 # Get a clean list of the file while printing its contents in
928 # reverse order. Last awk(1) in the pipeline eliminates
929 # non-consecutive lines: duplicates, blank lines, and colons.
930 # Comment lines beginning with '#' are allowed; parentheses
934 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
935 for( i=NF; i > 0; i-- ) print $i
936 }' "$1" |
awk '!s[$0]++'
941 # Complain if the package does not end in .tlz
943 if ! fnmatch
'*.tlz' "$1"
945 warn
"\`${1}' does not end in .tlz"
950 echo "{^} Upgrading $1 ..."
952 incoming
="$(basename -- "$1" .tlz)"; # Get the file name.
954 # Check package pre-existence
955 if test "$opt_force" != opt_force
&& \
956 test -e "$rootdir${packagedir}/$incoming"
959 " The package to be upgraded already exist;" \
960 "Unless the --force option is given, this package won't be UPGRADED."
964 # Ignore some signals until the upgrade process is complete
965 trap "" HUP INT QUIT ABRT TERM
967 # Check blacklisted packages before to proceed with the upgrade
969 echo "=== Checking blacklist ..."
970 for item
in $blacklist
975 " A blacklisted package name has been detected:" \
978 "^ This package will be INSTALLED instead of being upgraded ..."
979 opt_prune
=opt_prune mode_install
"$1"
986 # Prepare the package to install it in a temporary but custom location
988 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
989 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
991 echo "=== Pre-installing package in temporary location ..."
993 opt_prune
=opt_prune
; # Turns on prune operation.
995 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
996 _isUpgrade
=_isUpgrade.off
998 echo "=== Looking for installations under the same name ..."
999 for previous_package
in "$rootdir${packagedir}/${incoming%%_*}"_
*
1001 test -e "$previous_package" ||
continue
1003 # Previous package directory to be removed cannot be
1004 # the same to the temporary (private) location
1005 if test "$previous_package" = "$PRVLOC"
1010 # The directory of the package could be preserved if --keep
1011 mode_remove
"$previous_package"
1013 unset -v previous_package
1015 # Re-install incoming package to the final location
1018 opt_prune
=opt_prune.off
; # Turns off prune operation.
1020 echo "=== Deleting temporary location ..."
1021 if is_readable
"$PRVLOC"
1023 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1024 echo "removed directory: '$PRVLOC'"
1028 warn
"{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1031 # Reset given signals
1032 trap - HUP INT QUIT ABRT TERM
1037 # Complain if the package cannot be well-read
1039 is_readable
"$1" ||
exit 4
1041 # Complain if the package does not end in .tlz
1043 if ! fnmatch
'*.tlz' "$1"
1045 warn
"\`${1}' does not end in .tlz"
1049 # List content of files excluding directories
1052 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1061 # Perform sanity checks before package extraction
1063 is_readable
"$1" ||
exit 4
1066 warn
"\`${1}' is not a regular file."
1070 # Preparations to extract the package
1072 name
="$(basename -- "$1" .tlz)"
1074 # Set random directory under 'TMPDIR' using 'name'
1075 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1077 # Trap to remove 'PRVDIR' on disruptions
1078 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1080 # Create 'PRVDIR' removing access for all but user
1081 ( umask 077 ; mkdir
-- "$PRVDIR" )
1082 mkdir
-p -- "$PRVDIR"
1083 chmod 700 -- "$PRVDIR"
1086 echo "{-} Extracting package \`${name}' ..."
1088 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1091 # Try to remove (empty) 'PRVDIR' on failure
1096 warn
"" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1098 # Remove used variables
1099 unset -v name PRVDIR
1101 # Reset given signals
1102 trap - HUP INT ABRT TERM
1105 ### Extra functions to be used during the modes
1113 tar -tf "$file" > /dev
/null
&& \
1117 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1118 gzip -cd "$file" |
tar -tf - > /dev
/null
&& \
1119 gzip -cd "$file" |
tar -xpf -
1122 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1123 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& \
1124 bzip2 -cd "$file" |
tar -xpf -
1128 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& \
1129 lzip
-cd "$file" |
tar -xpf -
1133 xz
-cd "$file" |
tar -tf - > /dev
/null
&& \
1134 xz
-cd "$file" |
tar -xpf -
1137 *.
tar.zst |
*.tzst
)
1138 zstd
-cd "$file" |
tar -tf - > /dev
/null
&& \
1139 zstd
-cd "$file" |
tar -xpf -
1143 unzip -t "$file" > /dev
/null
&& \
1144 unzip "$file" > /dev
/null
1148 gzip -t "$file" && \
1149 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1153 gzip -t "$file" && \
1154 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1158 bzip2 -t "$file" && \
1159 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1163 lzip
-t "$file" && \
1164 lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1169 xz
-cd "$file" > "$(basename -- "$file" .xz)"
1173 zstd
-qt "$file" && \
1174 zstd
-cd "$file" > "$(basename -- "$file" .zst)"
1178 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1187 # Extract information from the recipe to create the meta file.
1189 # The package description is pre-formatted in 78 columns,
1190 # the '#' character and a space is added as prefix to conform
1191 # the 80 columns in total
1192 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1194 # Include build flags only if it is a real architecture
1195 if test "$arch" != noarch
1199 QICFLAGS=\"$QICFLAGS\"
1200 QICXXFLAGS=\"$QICXXFLAGS\"
1201 QILDFLAGS=\"$QILDFLAGS\"
1202 QICPPFLAGS=\"$QICPPFLAGS\""
1205 # Print saving the rest of the package information
1209 pkgversion=$pkgversion
1212 pkgcategory=\"${pkgcategory#@*}\"
1213 full_pkgname=$full_pkgname
1214 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1215 homepage=\"$homepage\"
1216 license=\"$license\"
1218 replace=\"$replace\"
1224 packagedir
=@PACKAGEDIR@
1225 targetdir
=@TARGETDIR@
1226 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1227 _rcfile
=@SYSCONFDIR@
/qirc
1228 opt_install
=opt_install.off
1229 opt_upgrade
=opt_upgrade.off
1230 opt_force
=opt_force.off
1231 opt_keep
=opt_keep.off
1232 opt_incr_release
=opt_incr_release.off
1233 opt_skipqsts
=opt_skipqsts.off
1234 opt_nopkg
=opt_nopkg.off
1235 opt_prune
=opt_prune.off
1246 _isUpgrade
=_isUpgrade.off
1249 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1250 QICFLAGS
="${QICFLAGS:--O2}"
1251 QICXXFLAGS
="${QICXXFLAGS:--O2}"
1252 QILDFLAGS
="${QILDFLAGS:-}"
1253 QICPPFLAGS
="${QICPPFLAGS:-}"
1254 worktree
=/usr
/src
/qi
1255 tardir
=${worktree}/sources
1256 outdir
=/var
/cache
/qi
/packages
1257 netget
="wget2 -c -w1 -t3 --no-check-certificate"
1258 rsync
="rsync -v -a -L -z -i --progress"
1259 tarlz_compression_options
="-9 --solid"
1260 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1265 # Store (default) directory locations
1266 QI_TARGETDIR
=$targetdir
1267 QI_PACKAGEDIR
=$packagedir
1268 QI_WORKTREE
=$worktree
1272 ### Parse commands and options
1278 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1279 "Switching to another command is not allowed (${1})."
1288 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1289 "Try '${PROGRAM} --help' for more information."
1294 validate_directory
()
1298 warn
"${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1308 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1311 warn
"${PROGRAM}: The '${name}' option has no defined value"
1315 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1319 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1335 validate_mode
install
1340 validate_mode remove
1345 validate_mode upgrade
1350 validate_mode extract
1354 validate_mode create
1367 _readconfig
=readconfig.off
1370 opt_install
=opt_install
1373 opt_upgrade
=opt_upgrade
1385 validate_option
"$1" "$2"
1387 validate_directory
"$1" "$packagedir"
1391 validate_option
"$1" "$2"
1392 packagedir
="${1#*=}"
1393 validate_directory
"$1" "$packagedir"
1396 validate_option
"$1" "$2"
1398 validate_directory
"$1" "$targetdir"
1402 validate_option
"$1" "$2"
1404 validate_directory
"$1" "$targetdir"
1407 validate_option
"$1" "$2"
1409 validate_directory
"$1" "$rootdir"
1413 validate_option
"$1" "$2"
1415 validate_directory
"$1" "$rootdir"
1418 validate_option
"$1" "$2"
1420 validate_directory
"$1" "$outdir"
1424 validate_option
"$1" "$2"
1426 validate_directory
"$1" "$outdir"
1429 validate_option
"$1" "$2"
1431 validate_directory
"$1" "$worktree"
1435 validate_option
"$1" "$2"
1437 validate_directory
"$1" "$worktree"
1440 validate_option
"$1" "$2"
1442 validate_directory
"$1" "$tardir"
1446 validate_option
"$1" "$2"
1448 validate_directory
"$1" "$tardir"
1450 --architecture |
-a )
1451 validate_option
"$1" "$2"
1456 validate_option
"$1" "$2"
1461 validate_digit
"$1" "$jobs"
1466 validate_digit
'-j' "$jobs"
1470 validate_digit
'--jobs=' "$jobs"
1476 opt_incr_release
=opt_incr_release
1478 --skip-questions |
-S )
1479 opt_skipqsts
=opt_skipqsts
1482 verbose_level
=$
(( verbose_level
+ 1 ))
1485 # A trick for a second -v.
1488 --show-location |
-L )
1490 "QI_TARGETDIR=$QI_TARGETDIR" \
1491 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1492 "QI_WORKTREE=$QI_WORKTREE" \
1493 "QI_TARDIR=$QI_TARDIR" \
1494 "QI_OUTDIR=$QI_OUTDIR"
1497 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' | \
1502 --version |
--versio |
--versi |
--vers | \
1503 -version |
-versio |
-versi |
-vers |
-V | version
)
1504 echo "$PROGRAM version @VERSION@"
1508 _readstdin
=readstdin
1513 break; # End of options.
1516 warn
"qi: Unrecognized option: $1" \
1517 "Try '${PROGRAM} --help' for more information."
1521 break; # No more options.
1526 unset -f readconfig validate_mode validate_option validate_directory validate_digit
1528 # When there are no arguments, show the help
1536 # Program sanity check
1538 for need
in awk basename chmod cp dirname find fold graft
grep \
1539 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1541 if ! \
command -v $need > /dev
/null
1543 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1549 # Set verbosity level and flags
1551 if test "$verbose_level" -gt 0
1553 if test "$verbose_level" -eq 1
1561 # Read standard input if FILE is -, or when
1562 # FILE is not connected to a terminal
1564 if test "$_readstdin" = readstdin
1568 warn
"qi: I won't read from a connected terminal." \
1569 "Try '${PROGRAM} --help' for more information."
1573 # Unset positional parameters setting $# to zero
1576 # Assign remaining arguments to the positional parameters
1579 set -- "$@" "$input"
1586 warn
"qi: We need at least one (valid) command." \
1587 "Try '${PROGRAM} --help' for more information."
1591 # Validate 'packagedir' and 'targetdir' as canonical directories
1593 # The single slash '/' does not qualify here
1594 if ! fnmatch
'/?*' "$packagedir"
1596 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1599 if test ! -d "$packagedir"
1601 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1605 # The single slash '/' is valid here
1606 if ! fnmatch
'/*' "$targetdir"
1608 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1611 if test ! -d "$targetdir"
1613 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1617 # Validate 'rootdir' directory
1619 if test -n "$rootdir"
1621 if test -d "$rootdir" && test "$rootdir" != /
1623 rootdir
="${rootdir%/}" # Remove slash from the end.
1625 # A workaround for graft-2.13+. The specified directory is
1626 # relative to the log file, we prepend it inside 'rootdir'
1628 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1629 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1631 # Compose 'rootdir' and log file option to be used with graft(1)
1632 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1634 # Unset variables coming from eval
1635 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1637 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1644 # Ensure 'TMPDIR' creation to prefix temporary files
1646 if test ! -d "$TMPDIR"
1648 mkdir
-p -- "$TMPDIR" || chkstatus_or_exit
1650 readonly TMPDIR packagedir targetdir
1652 # Process each package or recipe provided on the command-line