2 # Copyright (C) 2016-2022 Matias Fonzo <selk@dragora.org>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # 0 = Successful completion
19 # 1 = Minor common errors (e.g: help usage, support not available)
20 # 2 = Command execution error
21 # 3 = Integrity check error for compressed files
22 # 4 = File empty, not regular, or expected
23 # 5 = Empty or not defined variable
24 # 6 = Package already installed
25 # 10 = Network manager error
29 LC_ALL
=C
; # Override locale settings.
30 umask 022; # Remove write permission for group and other.
38 "Usage: $PROGRAM COMMAND [OPTIONS] [FILE]...
39 "'A simple but well-integrated package manager.
42 warn Warn about files that will be installed
43 install Install packages
44 remove Remove packages
45 upgrade Upgrade packages
46 extract Extract packages for debugging purposes
47 create Create a .tlz package from directory
48 build Build packages using recipe names
49 order Resolve build order through .order files
51 Options when installing, removing, or upgrading software packages:
52 -f, --force Force upgrade of pre-existing packages
53 -k, --keep Keep package directory when remove/upgrade
54 -p, --prune Prune conflicts
55 -P, --packagedir=<dir> Set directory for package installations
56 -t, --targetdir=<dir> Set target directory for symbolic links
57 -r, --rootdir=<dir> Use the fully qualified named directory as
58 the root directory for all qi operations
59 Note: the target directory and the package
60 directory will be relative to the specified
61 directory, excepting the graft log file
63 Options when building software packages using recipes:
64 -a, --architecture Set architecture name for the package
65 -j, --jobs Parallel jobs for the compiler
66 -k, --keep Keep ${srcdir} or ${destdir} when build
67 -S, --skip-questions Skip questions on completed recipes
68 -1, --increment Increment release number (${release} + 1)
69 -n, --no-package Do not create a .tlz package
70 -i, --install Install package after the build
71 -u, --upgrade Upgrade package after the build
72 -o, --outdir=<dir> Where the packages produced will be written
73 -w, --worktree=<dir> Where archives, patches, recipes are expected
74 -s, --sourcedir=<dir> Where compressed sources will be found
77 -N, --no-rc Do not read the configuration file
78 -v, --verbose Be verbose (an extra -v gives more)
79 -L, --show-location Print default directory locations and exit
80 -h, --help Display this help and exit
81 -V, --version Output version information and exit
83 Some influential environment variables:
84 TMPDIR Temporary directory for sources
85 QICFLAGS C compiler flags (to be used on CFLAGS)
86 QICXXFLAGS C++ compiler flags (to be used on CXXFLAGS)
87 QILDFLAGS Flags for the linker (to be used on LDFLAGS)
88 QICPPFLAGS C/C++ preprocessor flags (to be used on CPPFLAGS)
89 SOURCE_DATE_EPOCH Last modification time for created packages
91 When FILE is -, read standard input.
93 Exit status: 0 for a normal exit, 1 for minor common errors (help usage,
94 support not available, etc), 2 to indicate a command execution error;
95 3 for integrity check error on compressed files, 4 for empty, not
96 regular, or expected files, 5 for empty or not defined variables,
97 6 when a package already exist, 10 for network manager errors.
99 Qi home page: https://www.dragora.org
105 printf '%s\n' "$@" 1>&2
114 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
118 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
123 # Portable alternative to the file operator -nt (among shells)
126 if test -n "$(find "$1" -prune -newer "$2" -print)"
133 # Determine whether $2 matches pattern $1
150 if test $status -ne 0
152 echo "^ Return status = $status" 1>&2
153 exit "${1-2}"; # If not given, defaults to 2
161 if test "${_readconfig:-readconfig}" = readconfig
163 if is_readable
"$HOME/.qirc" 2> /dev
/null
165 _rcfile
="$HOME/.qirc"
168 echo "Importing configuration file from \`${_rcfile}' ..."
169 .
"$_rcfile" || chkstatus_or_exit
5
181 echo "{@} Building \"${recipe}\" ..."
183 # A recipe is any valid regular file. Qi sets priorities for reading
184 # a recipe, the order in which qi looks for a recipe is:
186 # 1. Current working directory.
188 # 2. If the specified path name does not contain "recipe" as the last
189 # component. Qi will complete it by adding "recipe" to the path
192 # 3. If the recipe is not in the current working directory, it will be
193 # searched under '${worktree}/recipes'. The last component will be
194 # completed adding "recipe" to the specified path name.
196 if test ! -f "$recipe"
198 if test -f "${recipe}/recipe"
200 recipe
="${recipe}/recipe"
201 elif test -f "${worktree}/recipes/${recipe}/recipe"
203 recipe
="${worktree}/recipes/${recipe}/recipe"
207 test -f "$recipe" ||
{
208 warn
"\`${recipe}' is not a regular file."
212 # Complain if the file name is not "recipe"
214 if test "${recipe##*/}" != recipe
216 warn
"\`${recipe}' is not a valid recipe name."
220 # Start preparations to import the recipe
222 # Get working directory and base name of the recipe
224 CWD
="$(CDPATH='' cd -P -- "$
(dirname -- "$recipe")" && pwd -P)"
225 recipe
="$(basename -- "$recipe")"
227 # Check readability for load the recipe on success
229 is_readable
"${CWD}/$recipe" ||
exit 4
231 # Re-create external directories
232 mkdir
-p -- "${worktree}/archive" \
233 "${worktree}/patches" \
234 "${worktree}/recipes" \
237 # Variables treatment for the current and the next recipe.
239 # Unset special variables that can only be predefined in
240 # the recipe and does not come from '${sysconfdir}/qirc'
243 srcdir destdir pkgname pkgversion pkgcategory program version release \
244 fetch description homepage license replace full_pkgname docs docsdir \
245 CFLAGS CXXFLAGS LDFLAGS CPPFLAGS
247 # The following variables must be restored, later
248 save_arch
="${save_arch:=$arch}"
249 save_jobs
="${save_jobs:=$jobs}"
250 save_outdir
="${save_outdir:=$outdir}"
251 save_opt_nopkg
="${save_opt_nopkg:=$opt_nopkg}"
253 # Reset variable values in case of return
256 outdir
="$save_outdir"
257 opt_nopkg
="$save_opt_nopkg"
259 # The following variables cannot be redefined on the recipe
260 readonly worktree netget rsync
267 # Check for required variables
268 if test -z "$program"
270 warn
"${recipe}: The variable 'program' is not defined."
273 if test -z "$version"
275 warn
"${recipe}: The variable 'version' is not defined."
278 if test -z "$release"
280 warn
"${recipe}: The variable 'release' is not defined."
284 # Pre-settings before to start building
286 # Increment the release number if the option was given
287 if test "$opt_incr_release" = opt_incr_release
289 release
=$
(( release
+ 1 ))
292 # Allow the dot as definition for 'tardir'
293 if test "$tardir" = .
298 # Set default values for the following special variables
300 pkgname
="${pkgname:=$program}"
301 pkgversion
="${pkgversion:=$version}"
302 srcdir
="${srcdir:=${program}-$version}"
303 destdir
="${destdir:=${TMPDIR}/package-$pkgname}"
305 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
306 if test -n "$pkgcategory"
308 pkgcategory
="@${pkgcategory}"
311 # Compose the full package name
312 full_pkgname
="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
314 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
315 outdir
="${outdir}/${arch}"
317 # If a package is going to be created the existence of a
318 # previous build will be detected and reported. Under normal
319 # conditions the recipe is built as long as it is newer than
320 # the produced package, if not, we warn to the user about it.
321 # Rebuilding the package is possible (through the force ;-)
323 if test "$opt_nopkg" != opt_nopkg
&& \
324 { test "$opt_force" != opt_force
&& \
325 test -e "${outdir}/${full_pkgname}.tlz" ; }
327 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz
"
331 "This recipe is
more RECENT than the produced package
:" \
333 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
334 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
336 " This recipe will be processed ...
" \
338 elif test -e "${CWD}/post-install
" && \
339 is_newer "${CWD}/post-install
" "${CWD}/$recipe"
343 "The post-install
script is
more RECENT than the recipe
:" \
345 "$
( stat
-c "%y %n" "${CWD}/post-install" )" \
346 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
348 " This recipe will be re-processed ...
" \
350 touch "${CWD}/$recipe"
351 elif test "$opt_skipqsts" = opt_skipqsts
353 warn "Recipe
for '${full_pkgname}.tlz': [Ignored
].
" ""
358 "This recipe ALREADY produced a package
:" \
359 "$
( stat
-c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
361 "The recipe is still OLDER than the produced package
:" \
362 "$
( stat
-c "%y %n" "${CWD}/$recipe" )" \
364 " Probably nothing has changed.
" \
367 # In non-interactive mode, the user is asked about
368 # rebuilding the package. In interactive mode,
369 # the user need to pass the option explicitly
373 "Do you want to rebuild this package?
" \
375 "2) No
, skip it
[default
]" \
376 "3) Resume
, skipping completed recipes
" \
377 "Enter an option number
:" > /dev/tty
378 IFS= read -r ANSWER < /dev/tty || exit 2;
381 echo "$ANSWER" > /dev/tty
386 echo "=== Building unprocessed or recently modified recipe
(s
) ...
" > /dev/tty
388 opt_skipqsts=opt_skipqsts
389 readonly opt_skipqsts
394 echo "Recipe
for '${full_pkgname}.tlz': Cancelled.
" > /dev/tty
400 warn "Use the
--force option to reprocess
${CWD}/${recipe}.
" ""
406 # Fetch remote sources
408 echo "=== Fetching remote sources
if needed ...
"
413 _source="${origin##*/}"
415 echo "=== Looking
for \"$_source\" ...
"
417 echo "=== Verifying checksum
file \
`${tardir}/${_source}.sha256'"
418 if test -e "${tardir}/${_source}.sha256"
420 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
424 warn " Checksum file \`${_source}.sha256
' does not exist"
427 # Download source or resume if allowed
429 if test ! -e "${tardir}/$_source"
431 warn "=== Attempting to get it from $origin ..."
437 cd -- "$tardir" && $rsync "$origin" || exit $?
438 sha256sum "$_source" > "${_source}.sha256"
439 ); chkstatus_or_exit 10
443 cd -- "$tardir" && $netget "$origin" || exit $?
444 sha256sum "$_source" > "${_source}.sha256"
445 ); chkstatus_or_exit 10
448 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
452 unset -v origin _source
454 warn "${recipe}: The variable 'fetch
' is empty."
457 # Prepare special directories for build the source,
458 # the destination and the output of the package
460 echo "=== Preparing directories ..."
462 if test -z "$keep_srcdir"
464 if test -e "${TMPDIR}/$srcdir"
466 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
467 echo "removed directory: '${TMPDIR}/$srcdir'"
471 "WARNING: The variable 'keep_srcdir
' has been set (${keep_srcdir})."
474 if test -z "$keep_destdir"
476 if test -e "$destdir"
478 rm -r -- "$destdir" || chkstatus_or_exit
479 echo "removed directory: '$destdir'"
481 mkdir -p -- "$destdir" || chkstatus_or_exit
482 echo "mkdir: created directory '$destdir'"
485 if test ! -e "$outdir"
487 mkdir -p -- "$outdir" || chkstatus_or_exit
488 echo "mkdir: created directory '$outdir'"
491 echo "=== Changing to '${TMPDIR}' ..."
492 cd -- "$TMPDIR" || chkstatus_or_exit
494 # Set trap before to run the build() function in order
495 # to catch the return status, exit code 2 if fails
497 trap 'chkstatus_or_exit
2' EXIT HUP INT QUIT ABRT TERM
499 # Determine if the debugging indicators of the shell should be
500 # retained, assuming that it has been previously passed
502 _xtrace_flag=_xtrace_flag_is_set ;;
505 echo "=== Running the 'build
' function ..."
509 # Check recipe to run (extra) defined functions by the packager
511 $(awk '!/^build
[ (]/ && /^
[^
{}]+ *\
(\
)/{ gsub
(/[()]/, "", $1); print
$1 }' "${CWD}/$recipe")
513 # Check if it is a shell function
514 case $(LC_ALL=C type $_definition) in
516 # Call and undo the function after executing it
518 unset -f $_definition
524 # Turn off possible shell flags coming from the recipe
527 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
532 # Reset given signals
533 trap - EXIT HUP INT QUIT ABRT TERM
535 # If 'destdir
' is empty, the package won't be created
536 if rmdir -- "$destdir" 2> /dev
/null
538 warn
"The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
542 # Create (make) the package
544 if test "$opt_nopkg" != opt_nopkg
546 # Edit the recipe when 'release' is incremented
547 if test "$opt_incr_release" = opt_incr_release
549 echo ",s/^\\(release\\)=.*/\\1=${release}/"$
'\nw' | \
550 ed
"${CWD}/$recipe" || chkstatus_or_exit
553 mkdir
-p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
555 # Include a copy of the recipe into the package
556 cp -p "${CWD}/$recipe" \
557 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
558 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
561 # Produce a checksum file for recipe copy
562 cd -- "${destdir}/var/lib/qi" && \
563 sha256sum
"${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
564 ); chkstatus_or_exit
4
566 # Detect post-install script for inclusion
568 if test -f "${CWD}/post-install"
570 echo "${CWD}/post-install: Detected."
571 cp -p "${CWD}/post-install" \
572 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
573 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
577 # Detect declared package name(s) for later replacement
579 if test -n "$replace"
582 "=== The following package names has been declared for replacement:" \
585 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
588 printf '%s\n' "$replace" >> \
589 "${destdir}/var/lib/qi/${full_pkgname}.replace"
594 # Create (external) meta file for package information,
595 # make a copy of it for the package database
596 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
597 do_meta
> "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
598 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
599 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
601 # Produce the package
602 cd -- "$destdir" && mode_create
"${outdir}/${full_pkgname}.tlz"
605 # Back to the current working directory
606 cd -- "$CWD" || chkstatus_or_exit
608 echo "=== Deleting 'srcdir' or 'destdir' ..."
609 if test "$opt_keep" != opt_keep
611 if test -z "$keep_srcdir"
613 srcdir
="${srcdir%%/*}"; # Directory name without parents.
614 if test -e "${TMPDIR}/$srcdir"
616 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
617 echo "removed directory: '${TMPDIR}/$srcdir'"
620 if test -z "$keep_destdir"
622 if test -e "$destdir"
624 rm -rf -- "$destdir" || chkstatus_or_exit
625 echo "removed directory: '$destdir'"
630 " The following directories will be preserved:" \
631 "${TMPDIR}/$srcdir" \
634 "The '--keep' option has been used."
637 # Install or upgrade the package after build
638 if test "$opt_nopkg" != opt_nopkg
640 if test "$opt_install" = opt_install
642 mode_install
"${outdir}/${full_pkgname}.tlz"
643 elif test "$opt_upgrade" = opt_upgrade
645 mode_upgrade
"${outdir}/${full_pkgname}.tlz"
649 warn
"{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
654 directory
="$(dirname -- "$1")"
656 # Perform sanity checks
658 if ! fnmatch
'/?*' "$directory"
660 warn
"${PROGRAM}: Output directory \`${directory}' is not fully qualified"
663 is_readable
"$directory" ||
exit 4
665 name
="$(basename -- "$1")"
668 echo "{#} Creating package name \`${name}' ..."
670 if test "$name" = "${name%.tlz}"
672 warn
"Package format '$name' not supported." \
673 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
677 # Pass extra options to tarlz(1)
678 if test -n "$SOURCE_DATE_EPOCH"
680 tarlz_opts
="--mtime=@${SOURCE_DATE_EPOCH}"
683 ( umask 022 ; tarlz
--solid -9 $tarlz_opts -cvf - -- * ) \
684 > "${directory}/$name"
688 ( cd -- "$directory" && sha256sum
"$name" > "${name}.sha256" )
691 warn
"{#} Package \"${name}\" created on ${directory}." ""
693 # Remove used variables
694 unset -v directory name
699 expunge
="${packagedir}/$(basename -- "$1" .tlz)"
702 echo "{<} Removing \`$rootdir${expunge}' ..."
704 # Complain if the package directory does not exist
706 test -e "$rootdir${expunge}" ||
{
707 warn
"Package directory '$rootdir${expunge}' does not exist."
711 # Complain if the package directory cannot be well-read
713 is_readable
"$rootdir${expunge}" ||
exit 4
715 # Remove package from Graft control
717 # Scan for possible conflicts, stop if arise
718 if test "$opt_prune" != opt_prune
720 echo "=== Checking for possible conflicts ..."
721 if graft
-d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
725 " A conflict occurred during uninstallation;" \
726 "Unless the --prune option is given, this package will be PRESERVED."
731 # Remove objects (files, links or directories) from the target
732 # directory that are in conflict with the package directory
734 echo "=== Pruning any conflict ..."
735 graft
-p -D -u $graft_r -t "$targetdir" "$expunge"
738 echo "=== Disabling links ..."
739 graft
-d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
742 # Delete package directory
743 if test "$opt_keep" != opt_keep
745 echo "=== Deleting package directory ..."
746 if is_readable
"${rootdir}$expunge"
748 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
749 echo "removed directory: '${rootdir}$expunge'"
753 warn
"{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
759 # Complain if the package cannot be well-read
761 is_readable
"$1" ||
exit 4
763 # Complain if the package does not end in .tlz
765 if ! fnmatch
'*.tlz' "$1"
767 warn
"\`${1}' does not end in .tlz"
771 # Make preparations to install the package
774 echo "{>} Installing $1 ..."
776 echo "=== Checking tarball integrity ..."
777 tarlz
--missing-crc -tf "$1" > /dev
/null
780 # To accept random directory from the upgrade mode
782 _packagedir
="${_packagedir:=$packagedir}"
784 # Create package directory using 'name'
786 name
="$(basename -- "$1" .tlz)"; # Get the file name.
788 if ! test -d "$rootdir${_packagedir}/$name"
790 mkdir
-p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
791 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
794 # Scan for possible conflicts, stop if arise
795 if test "$opt_prune" != opt_prune
797 echo "=== Checking for possible conflicts ..."
798 if graft
-i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
802 " A conflict occurred during installation;" \
803 "Unless the --prune option is given, this package won't be LINKED."
808 echo "=== Decompressing package ..."
809 ( cd -- "$rootdir${_packagedir}/$name" && tarlz
-xpf - ) < "$1"
812 # Transite package to Graft control
814 # Remove objects (files, links or directories) from the target
815 # directory that are in conflict with the package directory
816 echo "=== Pruning any conflict ..."
817 graft
-p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
820 echo "=== Enabling symbolic links ..."
821 graft
-i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
824 # Avoid unnecessary runs coming from the upgrade mode,
825 # this is when the incoming package is **pre-installed**
827 if test "$_isUpgrade" != _isUpgrade.on
829 # Show package description
830 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
832 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
833 elif test -r "${1}.txt"
835 # From external meta file (current directory)
836 awk '/^#/' "${1}.txt"
838 warn
"Description file not found for '$name'."
841 # Check and run the post-install script if exist
842 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
844 echo "=== Running post-install script for \`${name}' ..."
846 # Rely on 'targetdir' if 'rootdir' is empty
847 cd -- "${rootdir:=$targetdir}"/ && \
848 .
"$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
852 # Check if there are declared packages for replacement
853 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
857 for replace
in "$rootdir${_packagedir}/${line%%_*}"_
*
859 if ! test -e "$replace"
861 warn
" Declared package \`${replace}' to be replaced does not exist. [Ignored]"
865 replace
="${replace##*/}"
867 # The search for the package to be replaced cannot
868 # be the same to the incoming package, even to the
869 # temporary location coming from the upgrade mode
870 if test "$replace" = "$name" || \
871 test "_x_${replace}" = "_x_${PRVLOC##*/}"
876 warn
"WARNING: Replacing package \`${replace}' ..."
878 # Since the links belongs to the new package, only
879 # those which are not in conflict can be deleted.
880 # To complete, we will remove the package directory
882 graft
-d -D -u $graft_r \
883 -t "$targetdir" "$replace" > /dev
/null
2>&1
885 rm -rf -- "$rootdir${_packagedir}/$replace"
887 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
891 warn
"{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
892 unset -v name _packagedir
; # Remove used variables.
897 # Complain if the file cannot be well-read
899 is_readable
"$1" ||
exit 4
901 # Complain if the file does not end in .order
903 if ! fnmatch
'*.order' "$1"
905 warn
"\`${1}' does not end in .order"
909 # Get a clean list of the file while printing its contents in
910 # reverse order. Last awk(1) in the pipeline eliminates
911 # non-consecutive lines: duplicates, blank lines, and colons.
912 # Comment lines beginning with '#' are allowed; parentheses
916 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
917 for( i=NF; i > 0; i-- ) print $i
918 }' "$1" |
awk '!s[$0]++'
923 # Complain if the package does not end in .tlz
925 if ! fnmatch
'*.tlz' "$1"
927 warn
"\`${1}' does not end in .tlz"
932 echo "{^} Upgrading $1 ..."
934 incoming
="$(basename -- "$1" .tlz)"; # Get the file name.
936 # Check package pre-existence
937 if test "$opt_force" != opt_force
&& \
938 test -e "$rootdir${packagedir}/$incoming"
941 " The package to be upgraded already exist;" \
942 "Unless the --force option is given, this package won't be UPGRADED."
946 # Ignore some signals until the upgrade process is complete
947 trap "" HUP INT QUIT ABRT TERM
949 # Check blacklisted packages before to proceed with the upgrade
951 echo "=== Checking blacklist ..."
952 for item
in $blacklist
957 " A blacklisted package name has been detected:" \
960 "^ This package will be INSTALLED instead of being upgraded ..."
961 opt_prune
=opt_prune mode_install
"$1"
968 # Prepare the package to install it in a temporary but custom location
970 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
971 PRVLOC
=$
(mktemp
-dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX
) ||
exit 2
973 echo "=== Pre-installing package in temporary location ..."
975 opt_prune
=opt_prune
; # Turns on prune operation.
977 _isUpgrade
=_isUpgrade.on mode_install
"$1" "$PRVLOC" > /dev
/null
978 _isUpgrade
=_isUpgrade.off
980 echo "=== Looking for installations under the same name ..."
981 for previous_package
in "$rootdir${packagedir}/${incoming%%_*}"_
*
983 test -e "$previous_package" ||
continue
985 # Previous package directory to be removed cannot be
986 # the same to the temporary (private) location
987 if test "$previous_package" = "$PRVLOC"
992 # The directory of the package could be preserved if --keep
993 mode_remove
"$previous_package"
995 unset -v previous_package
997 # Re-install incoming package to the final location
1000 opt_prune
=opt_prune.off
; # Turns off prune operation.
1002 echo "=== Deleting temporary location ..."
1003 if is_readable
"$PRVLOC"
1005 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1006 echo "removed directory: '$PRVLOC'"
1010 warn
"{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1013 # Reset given signals
1014 trap - HUP INT QUIT ABRT TERM
1019 # Complain if the package cannot be well-read
1021 is_readable
"$1" ||
exit 4
1023 # Complain if the package does not end in .tlz
1025 if ! fnmatch
'*.tlz' "$1"
1027 warn
"\`${1}' does not end in .tlz"
1031 # List content of files excluding directories
1034 tarlz
-tvvf "$1" |
awk '!/^drwx/'
1043 # Perform sanity checks before package extraction
1045 is_readable
"$1" ||
exit 4
1048 warn
"\`${1}' is not a regular file."
1052 # Preparations to extract the package
1054 name
="$(basename -- "$1" .tlz)"
1056 # Set random directory under 'TMPDIR' using 'name'
1057 PRVDIR
=$
(mktemp
-dp "$TMPDIR" ${name}.XXXXXXXXXXXX
) ||
exit 2
1059 # Trap to remove 'PRVDIR' on disruptions
1060 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1062 # Create 'PRVDIR' removing access for all but user
1063 ( umask 077 ; mkdir
-- "$PRVDIR" )
1064 mkdir
-p -- "$PRVDIR"
1065 chmod 700 -- "$PRVDIR"
1068 echo "{-} Extracting package \`${name}' ..."
1070 ( umask 000 ; cd -- "$PRVDIR" && tarlz
-xvf - ) < "$1"
1073 # Try to remove (empty) 'PRVDIR' on failure
1078 warn
"" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1080 # Remove used variables
1081 unset -v name PRVDIR
1083 # Reset given signals
1084 trap - HUP INT ABRT TERM
1087 ### Extra functions to be used during the modes
1095 tar -tf "$file" > /dev
/null
&& \
1099 *.
tar.gz |
*.tgz |
*.
tar.Z
)
1100 gzip -cd "$file" |
tar -tf - > /dev
/null
&& \
1101 gzip -cd "$file" |
tar -xpf -
1104 *.
tar.bz2 |
*.tbz2 |
*.tbz
)
1105 bzip2 -cd "$file" |
tar -tf - > /dev
/null
&& \
1106 bzip2 -cd "$file" |
tar -xpf -
1110 lzip
-cd "$file" |
tar -tf - > /dev
/null
&& \
1111 lzip
-cd "$file" |
tar -xpf -
1115 xz
-cd "$file" |
tar -tf - > /dev
/null
&& \
1116 xz
-cd "$file" |
tar -xpf -
1120 unzip -t "$file" > /dev
/null
&& \
1121 unzip "$file" > /dev
/null
1125 gzip -t "$file" && \
1126 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1130 gzip -t "$file" && \
1131 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1135 bzip2 -t "$file" && \
1136 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1140 lzip
-t "$file" && \
1141 lzip
-cd "$file" > "$(basename -- "$file" .lz)"
1146 xz
-cd "$file" > "$(basename -- "$file" .xz)"
1150 warn
"${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1159 # Extract information from the recipe to create the meta file.
1161 # The package description is pre-formatted in 78 columns,
1162 # the '#' character and a space is added as prefix to conform
1163 # the 80 columns in total
1164 printf '%s\n' "$description" |
fold -w 78 |
awk '$0="# " $0'
1166 # Include build flags only if it is a real architecture
1167 if test "$arch" != noarch
1171 QICFLAGS=\"$QICFLAGS\"
1172 QICXXFLAGS=\"$QICXXFLAGS\"
1173 QILDFLAGS=\"$QILDFLAGS\"
1174 QICPPFLAGS=\"$QICPPFLAGS\""
1177 # Print saving the rest of the package information
1181 pkgversion=$pkgversion
1184 pkgcategory=\"${pkgcategory#@*}\"
1185 full_pkgname=$full_pkgname
1186 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1187 homepage=\"$homepage\"
1188 license=\"$license\"
1190 replace=\"$replace\"
1196 packagedir
=@PACKAGEDIR@
1197 targetdir
=@TARGETDIR@
1198 blacklist
="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1199 _rcfile
=@SYSCONFDIR@
/qirc
1200 opt_install
=opt_install.off
1201 opt_upgrade
=opt_upgrade.off
1202 opt_force
=opt_force.off
1203 opt_keep
=opt_keep.off
1204 opt_incr_release
=opt_incr_release.off
1205 opt_skipqsts
=opt_skipqsts.off
1206 opt_nopkg
=opt_nopkg.off
1207 opt_prune
=opt_prune.off
1217 _isUpgrade
=_isUpgrade.off
1220 TMPDIR
="${TMPDIR:-/usr/src/qi/build}"
1221 QICFLAGS
="${QICFLAGS:--O2}"
1222 QICXXFLAGS
="${QICXXFLAGS:--O2}"
1223 QILDFLAGS
="${QILDFLAGS:-}"
1224 QICPPFLAGS
="${QICPPFLAGS:-}"
1225 worktree
=/usr
/src
/qi
1226 tardir
=${worktree}/sources
1227 outdir
=/var
/cache
/qi
/packages
1228 netget
="wget2 -c -w1 -t3 --no-check-certificate"
1229 rsync
="rsync -v -a -L -z -i --progress"
1230 configure_args
="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1235 # Store (default) directory locations
1236 QI_TARGETDIR
=$targetdir
1237 QI_PACKAGEDIR
=$packagedir
1238 QI_WORKTREE
=$worktree
1242 ### Parse commands and options
1248 warn
"${PROGRAM}: First defined command: ${mode#*_}" \
1249 "Switching to another command is not allowed (${1})."
1258 warn
"${PROGRAM}: The '${1}' option requires an argument" \
1259 "Try '${PROGRAM} --help' for more information."
1264 validate_directory
()
1268 warn
"${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1278 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1281 warn
"${PROGRAM}: The '${name}' option has no defined value"
1285 warn
"${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1289 warn
"${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1305 validate_mode
install
1310 validate_mode remove
1315 validate_mode upgrade
1320 validate_mode extract
1324 validate_mode create
1337 _readconfig
=readconfig.off
1340 opt_install
=opt_install
1343 opt_upgrade
=opt_upgrade
1355 validate_option
"$1" "$2"
1357 validate_directory
"$1" "$packagedir"
1361 validate_option
"$1" "$2"
1362 packagedir
="${1#*=}"
1363 validate_directory
"$1" "$packagedir"
1366 validate_option
"$1" "$2"
1368 validate_directory
"$1" "$targetdir"
1372 validate_option
"$1" "$2"
1374 validate_directory
"$1" "$targetdir"
1377 validate_option
"$1" "$2"
1379 validate_directory
"$1" "$rootdir"
1383 validate_option
"$1" "$2"
1385 validate_directory
"$1" "$rootdir"
1388 validate_option
"$1" "$2"
1390 validate_directory
"$1" "$outdir"
1394 validate_option
"$1" "$2"
1396 validate_directory
"$1" "$outdir"
1399 validate_option
"$1" "$2"
1401 validate_directory
"$1" "$worktree"
1405 validate_option
"$1" "$2"
1407 validate_directory
"$1" "$worktree"
1410 validate_option
"$1" "$2"
1412 validate_directory
"$1" "$tardir"
1416 validate_option
"$1" "$2"
1418 validate_directory
"$1" "$tardir"
1420 --architecture |
-a )
1421 validate_option
"$1" "$2"
1426 validate_option
"$1" "$2"
1431 validate_digit
"$1" "$jobs"
1436 validate_digit
'-j' "$jobs"
1440 validate_digit
'--jobs=' "$jobs"
1446 opt_incr_release
=opt_incr_release
1448 --skip-questions |
-S )
1449 opt_skipqsts
=opt_skipqsts
1452 verbose_level
=$
(( verbose_level
+ 1 ))
1455 # A trick for a second -v.
1458 --show-location |
-L )
1460 "QI_TARGETDIR=$QI_TARGETDIR" \
1461 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1462 "QI_WORKTREE=$QI_WORKTREE" \
1463 "QI_TARDIR=$QI_TARDIR" \
1464 "QI_OUTDIR=$QI_OUTDIR"
1467 --help |
--hel |
--he |
--h |
'--?' |
-help |
-hel |
-he |
-h |
'-?' | \
1472 --version |
--versio |
--versi |
--vers | \
1473 -version |
-versio |
-versi |
-vers |
-V | version
)
1474 echo "$PROGRAM version @VERSION@"
1478 _readstdin
=readstdin
1483 break; # End of options.
1486 warn
"qi: Unrecognized option: $1" \
1487 "Try '${PROGRAM} --help' for more information."
1491 break; # No more options.
1497 readconfig validate_mode validate_option validate_directory validate_digit
1499 # When there are no arguments, show the help
1507 # Program sanity check
1509 for need
in awk basename chmod cp dirname find fold graft
grep \
1510 mkdir mktemp
rm rmdir sed sha256sum stat tarlz
; \
1512 if ! \
command -v $need > /dev
/null
1514 warn
"${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1520 # Set verbosity level and flags
1522 if test "$verbose_level" -gt 0
1524 if test "$verbose_level" -eq 1
1532 # Read standard input if FILE is -, or when
1533 # FILE is not connected to a terminal
1535 if test "$_readstdin" = readstdin
1539 warn
"qi: I won't read from a connected terminal." \
1540 "Try '${PROGRAM} --help' for more information."
1544 # Unset positional parameters setting $# to zero
1547 # Assign remaining arguments to the positional parameters
1550 set -- "$@" "$input"
1557 warn
"qi: We need at least one (valid) command." \
1558 "Try '${PROGRAM} --help' for more information."
1562 # Validate 'packagedir' and 'targetdir' as canonical directories
1564 # The single slash '/' does not qualify here
1565 if ! fnmatch
'/?*' "$packagedir"
1567 warn
"${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1570 if test ! -d "$packagedir"
1572 warn
"${PROGRAM}: Package directory \`${packagedir}' does not exist"
1576 # The single slash '/' is valid here
1577 if ! fnmatch
'/*' "$targetdir"
1579 warn
"${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1582 if test ! -d "$targetdir"
1584 warn
"${PROGRAM}: Target directory \`${targetdir}' does not exist"
1588 # Validate 'rootdir' directory
1590 if test -n "$rootdir"
1592 if test -d "$rootdir" && test "$rootdir" != /
1594 rootdir
="${rootdir%/}" # Remove slash from the end.
1596 # A workaround for graft-2.13+. The specified directory is
1597 # relative to the log file, we prepend it inside 'rootdir'
1599 eval "$(graft -L)" ; GRAFT_LOGFILE
="${GRAFT_LOGFILE:=/var/log/graft}"
1600 mkdir
-p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1602 # Compose 'rootdir' and log file option to be used with graft(1)
1603 graft_r
="-r $rootdir -l $GRAFT_LOGFILE"
1605 # Unset variables coming from eval
1606 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1608 warn
"${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1615 # Ensure 'TMPDIR' creation to prefix temporary files
1617 if test ! -d "$TMPDIR"
1619 mkdir
-p -- "$TMPDIR" || chkstatus_or_exit
1621 readonly TMPDIR packagedir targetdir
1623 # Process each package or recipe provided on the command-line