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
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 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
176 # Complain if the file cannot be well-read
177 is_readable
"$1" ||
exit 4
179 # Complain if the file does not end at expected extension
180 if ! fnmatch
"*.${2}" "$1"
182 echo "${PROGRAM}: \`${1}' does not end in .${2}" 1>&2
186 # Complain if the file is not regular
189 echo "${PROGRAM}: \`${1}' is not a regular file." 1>&2
198 ( umask 022 ; mkdir
-p -- "$1" ||
exit $?
); chkstatus_or_exit
209 echo "{@} Building \"${recipe}\" ..."
211 # A recipe is any valid regular file. Qi sets priorities for reading
212 # a recipe, the order in which qi looks for a recipe is:
214 # 1. Current working directory.
216 # 2. If the specified path name does not contain "recipe" as the last
217 # component. Qi will complete it by adding "recipe" to the path
220 # 3. If the recipe is not in the current working directory, it will be
221 # searched under '${worktree}/recipes'. The last component will be
222 # completed adding "recipe" to the specified path name.
224 if test ! -f "$recipe"
226 if test -e "${recipe}/recipe"
228 recipe
="${recipe}/recipe"
229 elif test -e "${worktree}/recipes/${recipe}/recipe"
231 recipe
="${worktree}/recipes/${recipe}/recipe"
235 test -f "$recipe" ||
{
236 warn
"\`${recipe}' is not a regular file."
240 # Complain if the file name is not "recipe"
242 if test "${recipe##*/}" != recipe
244 warn
"\`${recipe}' is not a valid recipe name."
248 # Start preparations to import the recipe
250 # Get working directory and base name of the recipe
252 CWD
="$(CDPATH='' cd -P -- "$
(dirname -- "$recipe")" && pwd -P)"
253 recipe
="$(basename -- "$recipe")"
255 # Check readability for load the recipe on success
257 is_readable
"${CWD}/$recipe" ||
exit 4
259 # Create external directories if needed
260 for directory
in "${worktree}/archive" "${worktree}/patches" "${worktree}/recipes" "$tardir"
262 make_directory
"$directory"
266 # Variables treatment for the current and the next recipe.
268 # Unset special variables that can only be predefined in
269 # the recipe and does not come from '${sysconfdir}/qirc'
272 srcdir destdir pkgname pkgversion pkgcategory program version release \
273 fetch description homepage license replace full_pkgname docs docsdir \
274 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
276 # The following variables must be restored, later
277 save_arch
="${save_arch:=$arch}"
278 save_jobs
="${save_jobs:=$jobs}"
279 save_outdir
="${save_outdir:=$outdir}"
280 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
281 save_tarlz_compression_options
="${save_tarlz_compression_options:=$tarlz_compression_options}"
283 # Reset variable values in case of return
286 outdir
="$save_outdir"
287 opt_nopkg
="$save_opt_nopkg"
288 tarlz_compression_options
="$save_tarlz_compression_options"
290 # The following variables cannot be redefined on a recipe
291 readonly worktree netget rsync
298 # Check if 'opt_skiprecipe' has been declared to ignore
299 # the current recipe and continue with the next recipe
300 if test "$opt_skiprecipe" = "opt_skiprecipe"
302 unset -v opt_skiprecipe
303 warn
"${recipe}: The variable 'opt_skiprecipe' has been used here."
307 # Check for required variables
308 if test -z "$program"
310 warn
"${recipe}: The variable 'program' is not defined."
313 if test -z "$version"
315 warn
"${recipe}: The variable 'version' is not defined."
320 warn
"${recipe}: The variable 'arch' is not defined."
323 if test -z "$release"
325 warn
"${recipe}: The variable 'release' is not defined."
329 # Pre-settings before to start building
331 # Increment the release number if the option was given
332 if test "$opt_incr_release" = opt_incr_release
334 release
=$
(( release
+ 1 ))
337 # Allow the dot as definition for 'tardir'
338 if test "$tardir" = .
343 # Set default values for the following special variables
345 pkgname
="${pkgname:=$program}"
346 pkgversion
="${pkgversion:=$version}"
347 srcdir
="${srcdir:=${program}-$version}"
348 srcdir
="${TMPDIR}/$srcdir"
349 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
351 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
352 if test -n "$pkgcategory"
354 pkgcategory
="@${pkgcategory}"
357 # Compose the full package name
358 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
360 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
361 outdir
="${outdir}/${arch}"
363 # If a package is going to be created the existence of a
364 # previous build will be detected and reported. Under normal
365 # conditions the recipe is built as long as it is newer than
366 # the produced package, if not, we warn to the user about it.
367 # Rebuilding the package is possible (through the force ;-)
369 if test "$opt_nopkg" != opt_nopkg
&& \
370 { test "$opt_force" != opt_force
&& \
371 test -e "${outdir}/${full_pkgname}.tlz" ; }
373 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
377 "This recipe is
more RECENT than the produced package
:" \
379 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
380 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
382 " This recipe will be processed ...
" \
384 elif test -e "${CWD}/post-install
" && \
385 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
389 "The post-install
script is
more RECENT than the recipe
:" \
391 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
392 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
394 " This recipe will be re-processed ...
" \
396 touch "${CWD}/$recipe"
397 elif test "$opt_skipqsts" = opt_skipqsts
399 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
404 "This recipe ALREADY produced a package
:" \
405 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
407 "The recipe is still OLDER than the produced package
:" \
408 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
410 " Probably nothing has changed.
" \
413 # In non-interactive mode the user is asked about
414 # rebuilding the package; on interactive mode,
415 # the user need to pass the option explicitly.
419 warn "Use the
--force option to reprocess
${CWD}/${recipe}.
" ""
424 "Do you want to rebuild this package? ...
" \
425 "(1) Yes
, built it
" \
426 "(2) No
, skip it.
[Default
]" \
427 "(3) Resume
(skipping completed recipes
)" \
428 "Enter an option number
:" > /dev/tty
429 IFS= read -r ANSWER < /dev/tty || exit 2;
432 echo "$ANSWER" > /dev/tty
437 echo "=== Building unprocessed or recently modified recipe
(s
) ...
" > /dev/tty
439 opt_skipqsts=opt_skipqsts
440 readonly opt_skipqsts
445 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
453 # Fetch remote sources
455 echo "=== Fetching remote sources
if needed ...
"
460 echo "=== Looking
for $origin ...
"
462 _source="${origin##*/}"; # Get the file name.
464 echo "=== Verifying checksum
file found \
`${tardir}/${_source}.sha256'"
465 if test -e "${tardir}/${_source}.sha256"
467 ( cd -- "$tardir" && sha256sum -c "${_source}.sha256" )
472 warn " Checksum file \`${_source}.sha256
' does not exist"
474 # Download source or resume, if allowed
476 if test ! -e "${tardir}/$_source"
478 warn "=== Attempting to get it from $origin ..."
484 cd -- "$tardir" && $rsync "$origin" || exit $?
485 sha256sum "$_source" > "${_source}.sha256"
486 ); chkstatus_or_exit 10
490 cd -- "$tardir" && $netget "$origin" || exit $?
491 sha256sum "$_source" > "${_source}.sha256"
492 ); chkstatus_or_exit 10
495 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
499 unset -v origin _source
501 warn "${recipe}: The variable 'fetch
' is empty."
504 # Prepare special directories for build the source,
505 # the destination and the output of the package
507 echo "=== Preparing directories ..."
509 if test -z "$keep_srcdir"
513 rm -rf -- "$srcdir" || chkstatus_or_exit
514 echo "removed directory: '$srcdir'"
517 warn "WARNING: The variable 'keep_srcdir
' has been set (${keep_srcdir})."
520 if test -z "$keep_destdir"
522 if test -e "$destdir"
524 rm -r -- "$destdir" || chkstatus_or_exit
525 echo "removed directory: '$destdir'"
527 make_directory "$destdir"
529 warn "WARNING: The variable 'keep_destdir
' has been set (${keep_destdir})."
532 make_directory "$outdir"
534 echo "=== Changing to '${TMPDIR}' ..."
535 cd -- "$TMPDIR" || chkstatus_or_exit
537 # Set trap before to run the build() function in order
538 # to catch the return status, exit code 2 if fails
540 trap 'chkstatus_or_exit
2' EXIT HUP INT QUIT ABRT TERM
542 # Determine if the debugging indicators of the shell should be
543 # retained, assuming that it has been previously passed
545 _xtrace_flag=_xtrace_flag_is_set ;;
548 echo "=== Running the 'build
' function ..."
552 # Check recipe to run (extra) defined functions by the packager
554 $(awk '!/^build
[ (]/ && /^
[^
{}]+ *\
(\
)/{ gsub
(/[()]/, "", $1); print
$1 }' "${CWD}/$recipe")
556 # Check if it is a shell function
557 case $(LC_ALL=C type $_definition) in
559 # Call and undo the function after executing it
561 unset -f $_definition
567 # Turn off possible shell flags coming from the recipe
570 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
575 # Reset given signals
576 trap - EXIT HUP INT QUIT ABRT TERM
578 # If 'destdir
' is empty, the package won't be created
579 if rmdir -- "$destdir" 2> /dev
/null
581 warn
"The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
585 # Create (make) the package
587 if test "$opt_nopkg" != opt_nopkg
589 # Edit the recipe when 'release' is incremented
590 if test "$opt_incr_release" = opt_incr_release
592 echo ",s/^\\(release\\)=.*/\\1=${release}/"$
'\nw' | \
593 ed
"${CWD}/$recipe" || chkstatus_or_exit
596 make_directory
"${destdir}/var/lib/qi"
598 # Include a copy of the recipe into the package
599 cp -p "${CWD}/$recipe" \
600 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
601 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
604 # Produce a checksum file for recipe copy
605 cd -- "${destdir}/var/lib/qi" && \
606 sha256sum
"${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
607 ); chkstatus_or_exit
4
609 # Detect post-install script for inclusion
611 if test -f "${CWD}/post-install"
613 echo "${CWD}/post-install: Detected."
614 cp -p "${CWD}/post-install" \
615 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
616 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
620 # Detect declared package name(s) for later replacement
622 if test -n "$replace"
625 "=== The following package names has been declared for replacement:" \
628 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
631 printf '%s\n' "$replace" >> \
632 "${destdir}/var/lib/qi/${full_pkgname}.replace"
637 # Create (external) meta file for package information,
638 # make a copy of it for the package database
639 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
640 do_meta
> "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
641 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
642 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
644 # Produce the package
645 cd -- "$destdir" && mode_create
"${outdir}/${full_pkgname}.tlz"
648 # Back to the current working directory
649 cd -- "$CWD" || chkstatus_or_exit
651 echo "=== Deleting 'srcdir' or 'destdir' ..."
652 if test "$opt_keep" != opt_keep
654 if test -z "$keep_srcdir"
658 rm -rf -- "$srcdir" || chkstatus_or_exit
659 echo "removed directory: '$srcdir'"
662 if test -z "$keep_destdir"
664 if test -e "$destdir"
666 rm -rf -- "$destdir" || chkstatus_or_exit
667 echo "removed directory: '$destdir'"
672 " The following directories will be preserved:" \
676 "The '--keep' option has been used."
679 # Install or upgrade the package after build
680 if test "$opt_nopkg" != opt_nopkg
682 if test "$opt_install" = opt_install
684 mode_install
"${outdir}/${full_pkgname}.tlz"
685 elif test "$opt_upgrade" = opt_upgrade
687 mode_upgrade
"${outdir}/${full_pkgname}.tlz"
691 warn
"{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
696 directory
="$(dirname -- "$1")"
698 # Perform sanity checks
700 if ! fnmatch
'/?*' "$directory"
702 warn
"${PROGRAM}: Output directory \`${directory}' is not fully qualified"
705 is_readable
"$directory" ||
exit 4
707 name
="$(basename -- "$1")"
710 echo "{#} Creating package name \`${name}' ..."
712 if test "$name" = "${name%.tlz}"
714 warn
"Package format '$name' not supported." \
715 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
719 # If needed, assign default values for compression options
720 tarlz_compression_options
="${tarlz_compression_options:=-9 --solid}"
722 # Pass extra options to tarlz(1)
723 if test -n "$SOURCE_DATE_EPOCH"
725 tarlz_compression_options
="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
728 ( umask 022 ; tarlz
$tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
731 ( cd -- "$directory" && sha256sum
"$name" > "${name}.sha256" )
734 warn
"{#} Package \"${name}\" created on ${directory}." ""
736 # Remove used variables
737 unset -v directory name
742 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
745 echo "{<} Removing \`$rootdir${expunge}' ..."
747 # Complain if the package directory cannot be well-read
749 is_readable
"$rootdir${expunge}" ||
exit 4
751 # Validate package directory as such
753 test -d "$rootdir${expunge}" ||
{
754 warn
"Package '$rootdir${expunge}' is not a valid directory."
758 # Remove package from Graft control
760 # Scan for possible conflicts, stop if arise
761 if test "$opt_prune" != opt_prune
763 echo "=== Checking for possible conflicts ..."
764 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
768 " A conflict occurred during uninstallation;" \
769 "Unless the --prune option is given, this package will be PRESERVED."
774 # Remove objects (files, links or directories) from the target
775 # directory that are in conflict with the package directory
777 echo "=== Pruning any conflict ..."
778 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
781 echo "=== Disabling links ..."
782 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
785 # Delete package directory
786 if test "$opt_keep" != opt_keep
788 echo "=== Deleting package directory ..."
789 if is_readable
"${rootdir}$expunge"
791 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
792 echo "removed directory: '${rootdir}$expunge'"
796 warn
"{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
802 validate_file
"$1" tlz
804 name
="$(basename -- "$1" .tlz)"
807 echo "{>} Installing package \`${name}.tlz' ..."
809 echo "=== Checking tarball integrity ..."
810 tarlz
--missing-crc -tf "$1" > /dev
/null
813 # To accept random directory from the upgrade mode
815 _packagedir
="${_packagedir:=$packagedir}"
817 make_directory
"$rootdir${_packagedir}/$name"
819 # Scan for possible conflicts, stop if arise
820 if test "$opt_prune" != opt_prune
822 echo "=== Checking for possible conflicts ..."
823 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
827 " A conflict occurred during installation;" \
828 "Unless the --prune option is given, this package won't be LINKED."
833 echo "=== Decompressing package ..."
834 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
837 # Transite package to Graft control
839 # Remove objects (files, links or directories) from the target
840 # directory that are in conflict with the package directory
841 echo "=== Pruning any conflict ..."
842 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
845 echo "=== Enabling symbolic links ..."
846 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
849 # Avoid unnecessary runs coming from the upgrade mode,
850 # this is when the incoming package is **pre-installed**
852 if test "$_isUpgrade" != _isUpgrade.on
854 # Show package description
855 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
857 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
858 elif test -r "${1}.txt"
860 # From external meta file (current directory)
861 awk '/^#/' "${1}.txt"
863 warn
"Description file not found for '$name'."
866 # Check and run the post-install script if exist
867 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
869 echo "=== Running post-install script for \`${name}' ..."
871 # Rely on 'targetdir' if 'rootdir' is empty
872 cd -- "${rootdir:=$targetdir}"/ && \
873 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
877 # Check if there are declared packages for replacement
878 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
882 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
884 if test ! -e "$replace"
886 warn
" Declared package \`${replace}' to be replaced does not exist. [Ignored]"
890 replace
="${replace##*/}"
892 # The search for the package to be replaced cannot
893 # be the same to the incoming package, even to the
894 # temporary location coming from the upgrade mode
895 if test "$replace" = "$name" || \
896 test "_x_${replace}" = "_x_${PRVLOC##*/}"
901 warn
"WARNING: Replacing package \`${replace}' ..."
903 # Since the links belongs to the new package, only
904 # those which are not in conflict can be deleted.
905 # To complete, we will remove the package directory
907 graft
-d -D -u $graft_r \
908 -t "$targetdir" "$replace" > /dev
/null
2>&1
910 rm -rf -- "$rootdir${_packagedir}/$replace"
912 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
916 warn
"{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
917 unset -v name _packagedir
; # Remove used variables.
922 validate_file
"$1" order
924 # Get a clean list of the file while printing its contents in
925 # reverse order. Last awk(1) in the pipeline eliminates
926 # non-consecutive lines: duplicates, blank lines, and colons.
927 # Comment lines beginning with '#' are allowed; parentheses
931 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
932 for( i=NF; i > 0; i-- ) print $i
933 }' "$1" |
awk '!s[$0]++'
938 validate_file
"$1" tlz
940 incoming
="$(basename -- "$1" .tlz)"
943 echo "{^} Upgrading to package \`${incoming}.tlz' ..."
945 # Check package pre-existence
946 if test "$opt_force" != opt_force
&& \
947 test -e "$rootdir${packagedir}/$incoming"
950 " The package to be upgraded already exist;" \
951 "Unless the --force option is given, this package won't be UPGRADED."
955 # Ignore some signals until the upgrade process is complete
956 trap "" HUP INT QUIT ABRT TERM
958 # Check blacklisted packages before to proceed with the upgrade
960 echo "=== Checking blacklist ..."
961 for item
in $blacklist
966 " A blacklisted package name has been detected:" \
969 "^ This package will be INSTALLED instead of being upgraded ..."
970 opt_prune
=opt_prune mode_install
"$1"
977 # Prepare the package to install it in a temporary but custom location
979 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
980 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
982 echo "=== Pre-installing package in temporary location ..."
984 opt_prune
=opt_prune
; # Turns on prune operation.
986 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
987 _isUpgrade
=_isUpgrade.off
989 echo "=== Looking for installations under the same name ..."
990 for previous_package
in "$rootdir${packagedir}/${incoming%%_*}"_
*
992 test -e "$previous_package" ||
continue
994 # Previous package directory to be removed cannot be
995 # the same to the temporary (private) location
996 if test "$previous_package" = "$PRVLOC"
1001 # The directory of the package could be preserved if --keep
1002 mode_remove
"$previous_package"
1004 unset -v previous_package
1006 # Re-install incoming package to the final location
1009 opt_prune
=opt_prune.off
; # Turns off prune operation.
1011 echo "=== Deleting temporary location ..."
1012 if is_readable
"$PRVLOC"
1014 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1015 echo "removed directory: '$PRVLOC'"
1019 warn
"{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1022 # Reset given signals
1023 trap - HUP INT QUIT ABRT TERM
1028 validate_file
"$1" tlz
1031 echo "{!} Warning about the content of \`$(basename -- "$1")' ..."
1033 # List content of files excluding directories
1034 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1040 validate_file
"$1" tlz
1042 name
="$(basename -- "$1" .tlz)"
1045 echo "{-} Extracting from package \`${name}.tlz' ..."
1047 # Set random directory under 'TMPDIR' using 'name'
1048 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1050 # Trap to remove 'PRVDIR' on disruptions
1051 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1053 # Create 'PRVDIR' removing access for all but user
1054 ( umask 077 ; mkdir
-- "$PRVDIR" )
1055 mkdir
-p -- "$PRVDIR"
1056 chmod 700 -- "$PRVDIR"
1058 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1061 # Try to remove (empty) 'PRVDIR' on failure
1066 warn
"" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1068 # Remove used variables
1069 unset -v name PRVDIR
1071 # Reset given signals
1072 trap - HUP INT ABRT TERM
1075 ### Extra functions to be used during the modes
1083 tar -tf "$file" > /dev
/null
&& \
1087 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1088 gzip -cd "$file" |
tar -tf - > /dev
/null
&& \
1089 gzip -cd "$file" |
tar -xpf -
1092 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1093 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& \
1094 bzip2 -cd "$file" |
tar -xpf -
1098 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& \
1099 lzip
-cd "$file" |
tar -xpf -
1103 xz
-cd "$file" |
tar -tf - > /dev
/null
&& \
1104 xz
-cd "$file" |
tar -xpf -
1107 *.
tar.zst |
*.tzst
)
1108 zstd
-cd "$file" |
tar -tf - > /dev
/null
&& \
1109 zstd
-cd "$file" |
tar -xpf -
1113 unzip -t "$file" > /dev
/null
&& \
1114 unzip "$file" > /dev
/null
1118 gzip -t "$file" && \
1119 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1123 gzip -t "$file" && \
1124 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1128 bzip2 -t "$file" && \
1129 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1133 lzip
-t "$file" && \
1134 lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1139 xz
-cd "$file" > "$(basename -- "$file" .xz)"
1143 zstd
-qt "$file" && \
1144 zstd
-cd "$file" > "$(basename -- "$file" .zst)"
1148 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1157 # Extract information from the recipe to create the meta file.
1159 # The package description is pre-formatted in 78 columns,
1160 # the '#' character and a space is added as prefix to conform
1161 # the 80 columns in total
1162 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1164 # Include build flags only if it is a real architecture
1165 if test "$arch" != noarch
1169 QICFLAGS=\"$QICFLAGS\"
1170 QICXXFLAGS=\"$QICXXFLAGS\"
1171 QILDFLAGS=\"$QILDFLAGS\"
1172 QICPPFLAGS=\"$QICPPFLAGS\""
1175 # Print saving the rest of the package information
1179 pkgversion=$pkgversion
1182 pkgcategory=\"${pkgcategory#@*}\"
1183 full_pkgname=$full_pkgname
1184 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1185 homepage=\"$homepage\"
1186 license=\"$license\"
1188 replace=\"$replace\"
1194 packagedir
=@PACKAGEDIR@
1195 targetdir
=@TARGETDIR@
1196 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1197 _rcfile
=@SYSCONFDIR@
/qirc
1198 opt_install
=opt_install.off
1199 opt_upgrade
=opt_upgrade.off
1200 opt_force
=opt_force.off
1201 opt_keep
=opt_keep.off
1202 opt_incr_release
=opt_incr_release.off
1203 opt_skipqsts
=opt_skipqsts.off
1204 opt_nopkg
=opt_nopkg.off
1205 opt_prune
=opt_prune.off
1216 _isUpgrade
=_isUpgrade.off
1219 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1220 QICFLAGS
="${QICFLAGS:--O2}"
1221 QICXXFLAGS
="${QICXXFLAGS:--O2}"
1222 QILDFLAGS
="${QILDFLAGS:-}"
1223 QICPPFLAGS
="${QICPPFLAGS:-}"
1224 worktree
=/usr
/src
/qi
1225 tardir
=${worktree}/sources
1227 netget
="wget2 -c -w1 -t3 --no-check-certificate"
1228 rsync
="rsync -v -a -L -z -i --progress"
1229 tarlz_compression_options
="-9 --solid"
1230 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1235 ### Parse commands and options
1241 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1242 "Switching to another command is not allowed (${1})."
1251 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1252 "Try '${PROGRAM} --help' for more information."
1257 validate_directory
()
1261 warn
"${PROGRAM}: Value \"${2}\" from '${1%%=*}' option must be a valid directory name"
1271 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1274 warn
"${PROGRAM}: The '${name}' option has no defined value"
1278 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1282 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1298 validate_mode
install
1303 validate_mode remove
1308 validate_mode upgrade
1313 validate_mode extract
1317 validate_mode create
1330 _readconfig
=readconfig.off
1333 opt_install
=opt_install
1336 opt_upgrade
=opt_upgrade
1348 validate_option
"$1" "$2"
1350 validate_directory
"$1" "$packagedir"
1354 validate_option
"$1" "$2"
1355 packagedir
="${1#*=}"
1356 validate_directory
"$1" "$packagedir"
1359 validate_option
"$1" "$2"
1361 validate_directory
"$1" "$targetdir"
1365 validate_option
"$1" "$2"
1367 validate_directory
"$1" "$targetdir"
1370 validate_option
"$1" "$2"
1372 validate_directory
"$1" "$rootdir"
1376 validate_option
"$1" "$2"
1378 validate_directory
"$1" "$rootdir"
1381 validate_option
"$1" "$2"
1383 validate_directory
"$1" "$outdir"
1387 validate_option
"$1" "$2"
1389 validate_directory
"$1" "$outdir"
1392 validate_option
"$1" "$2"
1394 validate_directory
"$1" "$worktree"
1398 validate_option
"$1" "$2"
1400 validate_directory
"$1" "$worktree"
1403 validate_option
"$1" "$2"
1405 validate_directory
"$1" "$tardir"
1409 validate_option
"$1" "$2"
1411 validate_directory
"$1" "$tardir"
1413 --architecture |
-a )
1414 validate_option
"$1" "$2"
1419 validate_option
"$1" "$2"
1424 validate_digit
"$1" "$jobs"
1429 validate_digit
'-j' "$jobs"
1433 validate_digit
'--jobs=' "$jobs"
1439 opt_incr_release
=opt_incr_release
1441 --skip-questions |
-S )
1442 opt_skipqsts
=opt_skipqsts
1445 verbose_level
=$
(( verbose_level
+ 1 ))
1448 # A trick for a second -v.
1451 --show-location |
-L )
1454 "QI_TARGETDIR=$targetdir" \
1455 "QI_PACKAGEDIR=$packagedir" \
1456 "QI_WORKTREE=$worktree" \
1457 "QI_TARDIR=$tardir" \
1461 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' | \
1466 --version |
--versio |
--versi |
--vers | \
1467 -version |
-versio |
-versi |
-vers |
-V | version
)
1468 echo "$PROGRAM version @VERSION@"
1472 _readstdin
=readstdin
1477 break; # End of options.
1480 warn
"qi: Unrecognized option: $1" \
1481 "Try '${PROGRAM} --help' for more information."
1485 break; # No more options.
1490 unset -f readconfig validate_mode validate_option validate_directory validate_digit
1492 # When there are no arguments, show the help
1500 # Program sanity check
1502 for need
in awk basename chmod cp dirname find fold graft
grep \
1503 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1505 if ! \
command -v $need > /dev
/null
1507 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1513 # Set verbosity level and flags
1515 if test "$verbose_level" -gt 0
1517 if test "$verbose_level" -eq 1
1525 # Read standard input if FILE is -, or when
1526 # FILE is not connected to a terminal
1528 if test "$_readstdin" = readstdin
1532 warn
"qi: I won't read from a connected terminal." \
1533 "Try '${PROGRAM} --help' for more information."
1537 # Unset positional parameters setting $# to zero
1540 # Assign remaining arguments to the positional parameters
1543 set -- "$@" "$input"
1550 warn
"qi: We need at least one (valid) command." \
1551 "Try '${PROGRAM} --help' for more information."
1555 # Validate 'packagedir' and 'targetdir' as canonical directories
1557 # The single slash '/' does not qualify here
1558 if ! fnmatch
'/?*' "$packagedir"
1560 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1563 if test ! -d "$packagedir"
1565 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1569 # The single slash '/' is valid here
1570 if ! fnmatch
'/*' "$targetdir"
1572 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1575 if test ! -d "$targetdir"
1577 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1581 # Validate 'rootdir' directory
1583 if test -n "$rootdir"
1585 if test -d "$rootdir" && test "$rootdir" != /
1587 rootdir
="${rootdir%/}" # Remove slash from the end.
1589 # A workaround for graft-2.13+. The specified directory is
1590 # relative to the log file, we prepend it inside 'rootdir'
1592 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1593 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1595 # Compose 'rootdir' and log file option to be used with graft(1)
1596 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1598 # Unset variables coming from eval
1599 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1601 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1608 # Ensure 'TMPDIR' creation to prefix temporary files
1610 make_directory
"$TMPDIR"
1612 readonly TMPDIR packagedir targetdir
1614 # Process each package or recipe provided on the command-line