recipes: libs/lzlib: Create a .pc file for configure's kmod
[dragora.git] / qi / src / qi.in
blob80e8c5ecc6a75b71b1b9b8e00d2207a558932fd1
1 #! /bin/sh -
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/>.
17 # EXIT STATUS
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
27 PROGRAM="${0##*/}"
29 LC_ALL=C; # Override locale settings.
30 umask 022; # Remove write permission for group and other.
31 export LC_ALL
33 ### Functions
35 usage()
37 printf '%s' \
38 "Usage: $PROGRAM COMMAND [OPTIONS] [FILE]...
39 "'A simple but well-integrated package manager.
41 List of commands:
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
76 Other options:
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
103 warn()
105 printf '%s\n' "$@" 1>&2
108 is_readable()
110 if test ! -e "$1"
111 then
112 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
113 return 1
115 if test ! -r "$1"
116 then
117 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
118 return 1
120 return 0
123 # Portable alternative to the file operator -nt (among shells)
124 is_newer()
126 if test -n "$(find "$1" -prune -newer "$2" -print)"
127 then
128 return 0
130 return 1
133 # Determine whether $2 matches pattern $1
134 fnmatch()
136 case $2 in
138 return 0
141 return 1
143 esac
146 chkstatus_or_exit()
148 status=$?
150 if test $status -ne 0
151 then
152 echo "^ Return status = $status" 1>&2
153 exit "${1-2}"; # If not given, defaults to 2
156 unset -v status
159 readconfig()
161 if test "${_readconfig:-readconfig}" = readconfig
162 then
163 if is_readable "$HOME/.qirc" 2> /dev/null
164 then
165 _rcfile="$HOME/.qirc"
168 echo "Importing configuration file from \`${_rcfile}' ..."
169 . "$_rcfile" || chkstatus_or_exit 5
171 unset -v _readconfig
174 validate_file()
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"
181 then
182 echo "${PROGRAM}: \`${1}' does not end in .${2}" 1>&2
183 exit 4
186 # Complain if the file is not regular
187 if test ! -f "$1"
188 then
189 echo "${PROGRAM}: \`${1}' is not a regular file." 1>&2
190 exit 4
194 make_directory()
196 if test ! -d "$1"
197 then
198 ( umask 022 ; mkdir -p -- "$1" || exit $? ); chkstatus_or_exit
202 ### Mode functions
204 mode_build()
206 recipe=$1
208 echo ""
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
218 # name.
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"
225 then
226 if test -e "${recipe}/recipe"
227 then
228 recipe="${recipe}/recipe"
229 elif test -e "${worktree}/recipes/${recipe}/recipe"
230 then
231 recipe="${worktree}/recipes/${recipe}/recipe"
235 test -f "$recipe" || {
236 warn "\`${recipe}' is not a regular file."
237 exit 4
240 # Complain if the file name is not "recipe"
242 if test "${recipe##*/}" != recipe
243 then
244 warn "\`${recipe}' is not a valid recipe name."
245 exit 4
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"
263 done
264 unset -v 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'
271 unset -v \
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
284 arch="$save_arch"
285 jobs="$save_jobs"
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
293 # Import the recipe
295 . "${CWD}/$recipe"
296 chkstatus_or_exit
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"
301 then
302 unset -v opt_skiprecipe
303 warn "${recipe}: The variable 'opt_skiprecipe' has been used here."
304 return 0;
307 # Check for required variables
308 if test -z "$program"
309 then
310 warn "${recipe}: The variable 'program' is not defined."
311 exit 5
313 if test -z "$version"
314 then
315 warn "${recipe}: The variable 'version' is not defined."
316 exit 5
318 if test -z "$arch"
319 then
320 warn "${recipe}: The variable 'arch' is not defined."
321 exit 5
323 if test -z "$release"
324 then
325 warn "${recipe}: The variable 'release' is not defined."
326 exit 5
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
333 then
334 release=$(( release + 1 ))
337 # Allow the dot as definition for 'tardir'
338 if test "$tardir" = .
339 then
340 tardir="$CWD"
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"
353 then
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" ; }
372 then
373 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
374 then
375 warn \
376 "" \
377 "This recipe is more RECENT than the produced package:" \
378 "" \
379 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
380 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
381 "" \
382 " This recipe will be processed ..." \
384 elif test -e "${CWD}/post-install" && \
385 is_newer "${CWD}/post-install" "${CWD}/$recipe"
386 then
387 warn \
388 "" \
389 "The post-install script is more RECENT than the recipe:" \
390 "" \
391 "$( stat -c "%y %n" "${CWD}/post-install" )" \
392 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
393 "" \
394 " This recipe will be re-processed ..." \
396 touch "${CWD}/$recipe"
397 elif test "$opt_skipqsts" = opt_skipqsts
398 then
399 warn "Recipe for '${full_pkgname}.tlz': [Ignored]." ""
400 return 0
401 else
402 warn \
403 "" \
404 "This recipe ALREADY produced a package:" \
405 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
406 "" \
407 "The recipe is still OLDER than the produced package:" \
408 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
409 "" \
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.
417 if test -t 0
418 then
419 warn "Use the --force option to reprocess ${CWD}/${recipe}." ""
420 return 6;
423 printf '%s\n' \
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;
430 case $ANSWER in
432 echo "$ANSWER" > /dev/tty
433 unset -v ANSWER
436 unset -v ANSWER
437 echo "=== Building unprocessed or recently modified recipe(s) ..." > /dev/tty
438 echo ""
439 opt_skipqsts=opt_skipqsts
440 readonly opt_skipqsts
441 return 0
444 unset -v ANSWER
445 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
446 echo ""
447 return 0
449 esac
453 # Fetch remote sources
455 echo "=== Fetching remote sources if needed ..."
456 if test -n "$fetch"
457 then
458 for origin in $fetch
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"
466 then
467 ( cd -- "$tardir" && sha256sum -c "${_source}.sha256" )
468 chkstatus_or_exit
469 continue;
472 warn " Checksum file \`${_source}.sha256' does not exist"
474 # Download source or resume, if allowed
476 if test ! -e "${tardir}/$_source"
477 then
478 warn "=== Attempting to get it from $origin ..."
481 case $origin in
482 rsync://*)
484 cd -- "$tardir" && $rsync "$origin" || exit $?
485 sha256sum "$_source" > "${_source}.sha256"
486 ); chkstatus_or_exit 10
488 *://*)
490 cd -- "$tardir" && $netget "$origin" || exit $?
491 sha256sum "$_source" > "${_source}.sha256"
492 ); chkstatus_or_exit 10
495 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
496 exit 4
497 esac
498 done
499 unset -v origin _source
500 else
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"
510 then
511 if test -e "$srcdir"
512 then
513 rm -rf -- "$srcdir" || chkstatus_or_exit
514 echo "removed directory: '$srcdir'"
516 else
517 warn "WARNING: The variable 'keep_srcdir' has been set (${keep_srcdir})."
520 if test -z "$keep_destdir"
521 then
522 if test -e "$destdir"
523 then
524 rm -r -- "$destdir" || chkstatus_or_exit
525 echo "removed directory: '$destdir'"
527 make_directory "$destdir"
528 else
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
544 case $- in *x*)
545 _xtrace_flag=_xtrace_flag_is_set ;;
546 esac
548 echo "=== Running the 'build' function ..."
549 build
550 unset -f build
552 # Check recipe to run (extra) defined functions by the packager
553 for _definition in \
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
558 *function*)
559 # Call and undo the function after executing it
560 $_definition
561 unset -f $_definition
563 esac
564 done
565 unset -v _definition
567 # Turn off possible shell flags coming from the recipe
569 set +e
570 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
571 then
572 set +x
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
580 then
581 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
582 opt_nopkg=opt_nopkg
585 # Create (make) the package
587 if test "$opt_nopkg" != opt_nopkg
588 then
589 # Edit the recipe when 'release' is incremented
590 if test "$opt_incr_release" = opt_incr_release
591 then
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"
602 chkstatus_or_exit
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"
612 then
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"
617 chkstatus_or_exit
620 # Detect declared package name(s) for later replacement
622 if test -n "$replace"
623 then
624 warn \
625 "=== The following package names has been declared for replacement:" \
626 "$replace"
628 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
629 for item in $replace
631 printf '%s\n' "$replace" >> \
632 "${destdir}/var/lib/qi/${full_pkgname}.replace"
633 done
634 unset -v item
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
653 then
654 if test -z "$keep_srcdir"
655 then
656 if test -e "$srcdir"
657 then
658 rm -rf -- "$srcdir" || chkstatus_or_exit
659 echo "removed directory: '$srcdir'"
662 if test -z "$keep_destdir"
663 then
664 if test -e "$destdir"
665 then
666 rm -rf -- "$destdir" || chkstatus_or_exit
667 echo "removed directory: '$destdir'"
670 else
671 warn \
672 " The following directories will be preserved:" \
673 "$srcdir" \
674 "$destdir" \
675 "" \
676 "The '--keep' option has been used."
679 # Install or upgrade the package after build
680 if test "$opt_nopkg" != opt_nopkg
681 then
682 if test "$opt_install" = opt_install
683 then
684 mode_install "${outdir}/${full_pkgname}.tlz"
685 elif test "$opt_upgrade" = opt_upgrade
686 then
687 mode_upgrade "${outdir}/${full_pkgname}.tlz"
691 warn "{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
694 mode_create()
696 directory="$(dirname -- "$1")"
698 # Perform sanity checks
700 if ! fnmatch '/?*' "$directory"
701 then
702 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
703 exit 4
705 is_readable "$directory" || exit 4
707 name="$(basename -- "$1")"
709 echo ""
710 echo "{#} Creating package name \`${name}' ..."
712 if test "$name" = "${name%.tlz}"
713 then
714 warn "Package format '$name' not supported." \
715 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
716 exit 4
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"
724 then
725 tarlz_compression_options="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
728 ( umask 022 ; tarlz $tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
729 chkstatus_or_exit 3
731 ( cd -- "$directory" && sha256sum "$name" > "${name}.sha256" )
732 chkstatus_or_exit 4
734 warn "{#} Package \"${name}\" created on ${directory}." ""
736 # Remove used variables
737 unset -v directory name
740 mode_remove()
742 expunge="${packagedir}/$(basename -- "$1" .tlz)"
744 echo ""
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."
755 exit 4
758 # Remove package from Graft control
760 # Scan for possible conflicts, stop if arise
761 if test "$opt_prune" != opt_prune
762 then
763 echo "=== Checking for possible conflicts ..."
764 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
765 grep "^CONFLICT"
766 then
767 warn "" \
768 " A conflict occurred during uninstallation;" \
769 "Unless the --prune option is given, this package will be PRESERVED."
770 exit 6;
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"
779 chkstatus_or_exit 2
781 echo "=== Disabling links ..."
782 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
783 chkstatus_or_exit 2
785 # Delete package directory
786 if test "$opt_keep" != opt_keep
787 then
788 echo "=== Deleting package directory ..."
789 if is_readable "${rootdir}$expunge"
790 then
791 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
792 echo "removed directory: '${rootdir}$expunge'"
796 warn "{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
797 unset -v expunge
800 mode_install()
802 validate_file "$1" tlz
804 name="$(basename -- "$1" .tlz)"
806 echo ""
807 echo "{>} Installing package \`${name}.tlz' ..."
809 echo "=== Checking tarball integrity ..."
810 tarlz --missing-crc -tf "$1" > /dev/null
811 chkstatus_or_exit 3
813 # To accept random directory from the upgrade mode
814 _packagedir="$2"
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
821 then
822 echo "=== Checking for possible conflicts ..."
823 if graft -i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
824 grep "^CONFLICT"
825 then
826 warn "" \
827 " A conflict occurred during installation;" \
828 "Unless the --prune option is given, this package won't be LINKED."
829 exit 6;
833 echo "=== Decompressing package ..."
834 ( cd -- "$rootdir${_packagedir}/$name" && tarlz -xpf - ) < "$1"
835 chkstatus_or_exit 3
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"
843 chkstatus_or_exit 2
845 echo "=== Enabling symbolic links ..."
846 graft -i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
847 chkstatus_or_exit 2
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
853 then
854 # Show package description
855 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
856 then
857 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
858 elif test -r "${1}.txt"
859 then
860 # From external meta file (current directory)
861 awk '/^#/' "${1}.txt"
862 else
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"
868 then
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"
879 then
880 while read -r line
882 for replace in "$rootdir${_packagedir}/${line%%_*}"_*
884 if test ! -e "$replace"
885 then
886 warn " Declared package \`${replace}' to be replaced does not exist. [Ignored]"
887 continue;
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##*/}"
897 then
898 continue;
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"
911 done
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.
920 mode_order()
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
928 # are simply ignored
930 awk '{
931 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
932 for( i=NF; i > 0; i-- ) print $i
933 }' "$1" | awk '!s[$0]++'
936 mode_upgrade()
938 validate_file "$1" tlz
940 incoming="$(basename -- "$1" .tlz)"
942 echo ""
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"
948 then
949 warn "" \
950 " The package to be upgraded already exist;" \
951 "Unless the --force option is given, this package won't be UPGRADED."
952 exit 6;
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
963 case $item in
964 "${incoming%@*}"*)
965 warn \
966 " A blacklisted package name has been detected:" \
967 "$incoming" \
968 "" \
969 "^ This package will be INSTALLED instead of being upgraded ..."
970 opt_prune=opt_prune mode_install "$1"
971 return 0;
973 esac
974 done
975 unset -v item
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"
997 then
998 continue;
1001 # The directory of the package could be preserved if --keep
1002 mode_remove "$previous_package"
1003 done
1004 unset -v previous_package
1006 # Re-install incoming package to the final location
1008 mode_install "$1"
1009 opt_prune=opt_prune.off; # Turns off prune operation.
1011 echo "=== Deleting temporary location ..."
1012 if is_readable "$PRVLOC"
1013 then
1014 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1015 echo "removed directory: '$PRVLOC'"
1017 unset -v PRVLOC
1019 warn "{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1020 unset -v incoming
1022 # Reset given signals
1023 trap - HUP INT QUIT ABRT TERM
1026 mode_warn()
1028 validate_file "$1" tlz
1030 echo ""
1031 echo "{!} Warning about the content of \`$(basename -- "$1")' ..."
1033 # List content of files excluding directories
1034 tarlz -tvvf "$1" | awk '!/^drwx/'
1035 chkstatus_or_exit 3
1038 mode_extract()
1040 validate_file "$1" tlz
1042 name="$(basename -- "$1" .tlz)"
1044 echo ""
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"
1059 if test $? -ne 0
1060 then
1061 # Try to remove (empty) 'PRVDIR' on failure
1062 rmdir -- "$PRVDIR"
1063 exit 3;
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
1077 unpack()
1079 for file in "$@"
1081 case $file in
1082 *.tar)
1083 tar -tf "$file" > /dev/null && \
1084 tar -xpf "$file"
1085 chkstatus_or_exit 3
1087 *.tar.gz | *.tgz | *.tar.Z )
1088 gzip -cd "$file" | tar -tf - > /dev/null && \
1089 gzip -cd "$file" | tar -xpf -
1090 chkstatus_or_exit 3
1092 *.tar.bz2 | *.tbz2 | *.tbz )
1093 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1094 bzip2 -cd "$file" | tar -xpf -
1095 chkstatus_or_exit 3
1097 *.tar.lz | *.tlz )
1098 lzip -cd "$file" | tar -tf - > /dev/null && \
1099 lzip -cd "$file" | tar -xpf -
1100 chkstatus_or_exit 3
1102 *.tar.xz | *.txz )
1103 xz -cd "$file" | tar -tf - > /dev/null && \
1104 xz -cd "$file" | tar -xpf -
1105 chkstatus_or_exit 3
1107 *.tar.zst | *.tzst )
1108 zstd -cd "$file" | tar -tf - > /dev/null && \
1109 zstd -cd "$file" | tar -xpf -
1110 chkstatus_or_exit 3
1112 *.zip | *.ZIP )
1113 unzip -t "$file" > /dev/null && \
1114 unzip "$file" > /dev/null
1115 chkstatus_or_exit 3
1117 *.gz)
1118 gzip -t "$file" && \
1119 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1120 chkstatus_or_exit 3
1122 *.Z)
1123 gzip -t "$file" && \
1124 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1125 chkstatus_or_exit 3
1127 *.bz2)
1128 bzip2 -t "$file" && \
1129 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1130 chkstatus_or_exit 3
1132 *.lz)
1133 lzip -t "$file" && \
1134 lzip -cd "$file" > "$(basename -- "$file" .lz)"
1135 chkstatus_or_exit 3
1137 *.xz)
1138 xz -t "$file" && \
1139 xz -cd "$file" > "$(basename -- "$file" .xz)"
1140 chkstatus_or_exit 3
1142 *.zst)
1143 zstd -qt "$file" && \
1144 zstd -cd "$file" > "$(basename -- "$file" .zst)"
1145 chkstatus_or_exit 3
1148 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1149 exit 1
1150 esac
1151 done
1152 unset -v file
1155 do_meta()
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
1166 then
1167 printf '%s' \
1169 QICFLAGS=\"$QICFLAGS\"
1170 QICXXFLAGS=\"$QICXXFLAGS\"
1171 QILDFLAGS=\"$QILDFLAGS\"
1172 QICPPFLAGS=\"$QICPPFLAGS\""
1175 # Print saving the rest of the package information
1176 printf '%s' \
1178 pkgname=$pkgname
1179 pkgversion=$pkgversion
1180 arch=$arch
1181 release=$release
1182 pkgcategory=\"${pkgcategory#@*}\"
1183 full_pkgname=$full_pkgname
1184 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1185 homepage=\"$homepage\"
1186 license=\"$license\"
1187 fetch=\"$fetch\"
1188 replace=\"$replace\"
1192 ### Default values
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
1206 verbose_level=0
1207 opt_skiprecipe=""
1208 rootdir=""
1209 arch=@ARCH@
1210 jobs=1
1211 mode=""
1212 _xtrace_flag=""
1213 _readstdin=""
1214 graft_v=""
1215 graft_r=""
1216 _isUpgrade=_isUpgrade.off
1217 keep_srcdir=""
1218 keep_destdir=""
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
1226 outdir=@OUTDIR@
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@"
1231 infodir=@INFODIR@
1232 mandir=@MANDIR@
1233 docdir=@DOCDIR@
1235 ### Parse commands and options
1237 validate_mode()
1239 if test -n "$mode"
1240 then
1241 warn "${PROGRAM}: First defined command: ${mode#*_}" \
1242 "Switching to another command is not allowed (${1})."
1243 exit 1
1247 validate_option()
1249 if test -z "$2"
1250 then
1251 warn "${PROGRAM}: The '${1}' option requires an argument" \
1252 "Try '${PROGRAM} --help' for more information."
1253 exit 1
1257 validate_directory()
1259 if test ! -d "$2"
1260 then
1261 warn "${PROGRAM}: Value \"${2}\" from '${1%%=*}' option must be a valid directory name"
1262 exit 1
1266 validate_digit()
1268 name="$1"
1269 shift
1271 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1272 case "${1#[-+]}" in
1274 warn "${PROGRAM}: The '${name}' option has no defined value"
1275 exit 1
1277 *.*.*)
1278 warn "${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1279 exit 1
1281 *[!0-9]*)
1282 warn "${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1283 exit 1
1285 esac
1287 unset -v name
1290 while test $# -gt 0
1292 case $1 in
1293 warn)
1294 validate_mode warn
1295 mode=mode_warn
1297 install)
1298 validate_mode install
1299 readconfig
1300 mode=mode_install
1302 remove)
1303 validate_mode remove
1304 readconfig
1305 mode=mode_remove
1307 upgrade)
1308 validate_mode upgrade
1309 readconfig
1310 mode=mode_upgrade
1312 extract)
1313 validate_mode extract
1314 mode=mode_extract
1316 create)
1317 validate_mode create
1318 mode=mode_create
1320 order)
1321 validate_mode order
1322 mode=mode_order
1324 build)
1325 validate_mode build
1326 readconfig
1327 mode=mode_build
1329 --no-rc | -N )
1330 _readconfig=readconfig.off
1332 --install | -i )
1333 opt_install=opt_install
1335 --upgrade | -u )
1336 opt_upgrade=opt_upgrade
1338 --force | -f )
1339 opt_force=opt_force
1341 --keep | -k )
1342 opt_keep=opt_keep
1344 --prune | -p )
1345 opt_prune=opt_prune
1347 --packagedir | -P )
1348 validate_option "$1" "$2"
1349 packagedir="$2"
1350 validate_directory "$1" "$packagedir"
1351 shift
1353 --packagedir=*)
1354 validate_option "$1" "$2"
1355 packagedir="${1#*=}"
1356 validate_directory "$1" "$packagedir"
1358 --targetdir | -t )
1359 validate_option "$1" "$2"
1360 targetdir="$2"
1361 validate_directory "$1" "$targetdir"
1362 shift
1364 --targetdir=*)
1365 validate_option "$1" "$2"
1366 targetdir="${1#*=}"
1367 validate_directory "$1" "$targetdir"
1369 --rootdir | -r )
1370 validate_option "$1" "$2"
1371 rootdir="$2"
1372 validate_directory "$1" "$rootdir"
1373 shift
1375 --rootdir=*)
1376 validate_option "$1" "$2"
1377 rootdir="${1#*=}"
1378 validate_directory "$1" "$rootdir"
1380 --outdir | -o )
1381 validate_option "$1" "$2"
1382 outdir="$2"
1383 validate_directory "$1" "$outdir"
1384 shift
1386 --outdir=*)
1387 validate_option "$1" "$2"
1388 outdir="${1#*=}"
1389 validate_directory "$1" "$outdir"
1391 --worktree | -w )
1392 validate_option "$1" "$2"
1393 worktree="$2"
1394 validate_directory "$1" "$worktree"
1395 shift
1397 --worktree=*)
1398 validate_option "$1" "$2"
1399 worktree="${1#*=}"
1400 validate_directory "$1" "$worktree"
1402 --sourcedir | -s )
1403 validate_option "$1" "$2"
1404 tardir="$2"
1405 validate_directory "$1" "$tardir"
1406 shift
1408 --sourcedir=*)
1409 validate_option "$1" "$2"
1410 tardir="${1#*=}"
1411 validate_directory "$1" "$tardir"
1413 --architecture | -a )
1414 validate_option "$1" "$2"
1415 arch="$2"
1416 shift
1418 --arch=*)
1419 validate_option "$1" "$2"
1420 arch="${1#*=}"
1422 --jobs | -j )
1423 jobs="$2"
1424 validate_digit "$1" "$jobs"
1425 shift
1427 -j[0-9]*)
1428 jobs="${1#-j*}"
1429 validate_digit '-j' "$jobs"
1431 --jobs=*)
1432 jobs="${1#*=}"
1433 validate_digit '--jobs=' "$jobs"
1435 --no-package | -n )
1436 opt_nopkg=opt_nopkg
1438 --increment | -1 )
1439 opt_incr_release=opt_incr_release
1441 --skip-questions | -S )
1442 opt_skipqsts=opt_skipqsts
1444 --verbose | -v )
1445 verbose_level=$(( verbose_level + 1 ))
1447 -vv)
1448 # A trick for a second -v.
1449 verbose_level=2
1451 --show-location | -L )
1452 readconfig
1453 printf '%s\n' \
1454 "QI_TARGETDIR=$targetdir" \
1455 "QI_PACKAGEDIR=$packagedir" \
1456 "QI_WORKTREE=$worktree" \
1457 "QI_TARDIR=$tardir" \
1458 "QI_OUTDIR=$outdir"
1459 exit 0
1461 --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' | \
1462 help )
1463 usage
1464 exit 0
1466 --version | --versio | --versi | --vers | \
1467 -version | -versio | -versi | -vers | -V | version )
1468 echo "$PROGRAM version @VERSION@"
1469 exit 0
1471 '-')
1472 _readstdin=readstdin
1473 break
1476 shift
1477 break; # End of options.
1480 warn "qi: Unrecognized option: $1" \
1481 "Try '${PROGRAM} --help' for more information."
1482 exit 1
1485 break; # No more options.
1487 esac
1488 shift
1489 done
1490 unset -f readconfig validate_mode validate_option validate_directory validate_digit
1492 # When there are no arguments, show the help
1493 if test $# -eq 0
1494 then
1495 usage
1496 exit 1
1498 unset -f usage
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
1506 then
1507 warn "${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1508 exit 2
1510 done
1511 unset -v need
1513 # Set verbosity level and flags
1515 if test "$verbose_level" -gt 0
1516 then
1517 if test "$verbose_level" -eq 1
1518 then
1519 graft_v=-v
1520 else
1521 graft_v=-V
1525 # Read standard input if FILE is -, or when
1526 # FILE is not connected to a terminal
1528 if test "$_readstdin" = readstdin
1529 then
1530 if test -t 0
1531 then
1532 warn "qi: I won't read from a connected terminal." \
1533 "Try '${PROGRAM} --help' for more information."
1534 exit 1
1537 # Unset positional parameters setting $# to zero
1538 set --
1540 # Assign remaining arguments to the positional parameters
1541 while read -r input
1543 set -- "$@" "$input"
1544 done
1546 unset -v _readstdin
1548 if test -z "$mode"
1549 then
1550 warn "qi: We need at least one (valid) command." \
1551 "Try '${PROGRAM} --help' for more information."
1552 exit 4
1555 # Validate 'packagedir' and 'targetdir' as canonical directories
1557 # The single slash '/' does not qualify here
1558 if ! fnmatch '/?*' "$packagedir"
1559 then
1560 warn "${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1561 exit 4
1563 if test ! -d "$packagedir"
1564 then
1565 warn "${PROGRAM}: Package directory \`${packagedir}' does not exist"
1566 exit 4
1569 # The single slash '/' is valid here
1570 if ! fnmatch '/*' "$targetdir"
1571 then
1572 warn "${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1573 exit 4
1575 if test ! -d "$targetdir"
1576 then
1577 warn "${PROGRAM}: Target directory \`${targetdir}' does not exist"
1578 exit 4
1581 # Validate 'rootdir' directory
1583 if test -n "$rootdir"
1584 then
1585 if test -d "$rootdir" && test "$rootdir" != /
1586 then
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
1600 else
1601 warn "${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1602 exit 4
1604 readonly rootdir
1605 export rootdir
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
1616 for package in "$@"
1618 $mode "$package"
1619 done