recipes: kernel: Use a separate kernel for the i586 architecture
[dragora.git] / qi / src / qi.in
blob0d96bc5861bf8a7c4a4363d6a66a14cea3678a05
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 if ! test -r "$1"
113 then
114 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
115 return 1
117 else
118 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
119 return 1
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 ### Mode functions
176 mode_build()
178 recipe=$1
180 echo ""
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
190 # name.
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"
197 then
198 if test -f "${recipe}/recipe"
199 then
200 recipe="${recipe}/recipe"
201 elif test -f "${worktree}/recipes/${recipe}/recipe"
202 then
203 recipe="${worktree}/recipes/${recipe}/recipe"
207 test -f "$recipe" || {
208 warn "\`${recipe}' is not a regular file."
209 exit 4
212 # Complain if the file name is not "recipe"
214 if test "${recipe##*/}" != recipe
215 then
216 warn "\`${recipe}' is not a valid recipe name."
217 exit 4
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" \
235 "$tardir"
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'
242 unset -v \
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
254 arch="$save_arch"
255 jobs="$save_jobs"
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
262 # Import the recipe
264 . "${CWD}/$recipe"
265 chkstatus_or_exit
267 # Check for required variables
268 if test -z "$program"
269 then
270 warn "${recipe}: The variable 'program' is not defined."
271 exit 5
273 if test -z "$version"
274 then
275 warn "${recipe}: The variable 'version' is not defined."
276 exit 5
278 if test -z "$arch"
279 then
280 warn "${recipe}: The variable 'arch' is not defined."
281 exit 5
283 if test -z "$release"
284 then
285 warn "${recipe}: The variable 'release' is not defined."
286 exit 5
289 # Pre-settings before to start building
291 # Increment the release number if the option was given
292 if test "$opt_incr_release" = opt_incr_release
293 then
294 release=$(( release + 1 ))
297 # Allow the dot as definition for 'tardir'
298 if test "$tardir" = .
299 then
300 tardir="$CWD"
303 # Set default values for the following special variables
305 pkgname="${pkgname:=$program}"
306 pkgversion="${pkgversion:=$version}"
307 srcdir="${srcdir:=${program}-$version}"
308 destdir="${destdir:=${TMPDIR}/package-$pkgname}"
310 # If 'pkgcategory' has been defined, prefix it using the "at" symbol
311 if test -n "$pkgcategory"
312 then
313 pkgcategory="@${pkgcategory}"
316 # Compose the full package name
317 full_pkgname="${full_pkgname:=${pkgname}_${pkgversion}_${arch}-${release}${pkgcategory}}"
319 # Use 'arch' as suffix for 'outdir' to have a well-organized package output
320 outdir="${outdir}/${arch}"
322 # If a package is going to be created the existence of a
323 # previous build will be detected and reported. Under normal
324 # conditions the recipe is built as long as it is newer than
325 # the produced package, if not, we warn to the user about it.
326 # Rebuilding the package is possible (through the force ;-)
328 if test "$opt_nopkg" != opt_nopkg && \
329 { test "$opt_force" != opt_force && \
330 test -e "${outdir}/${full_pkgname}.tlz" ; }
331 then
332 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
333 then
334 warn \
335 "" \
336 "This recipe is more RECENT than the produced package:" \
337 "" \
338 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
339 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
340 "" \
341 " This recipe will be processed ..." \
343 elif test -e "${CWD}/post-install" && \
344 is_newer "${CWD}/post-install" "${CWD}/$recipe"
345 then
346 warn \
347 "" \
348 "The post-install script is more RECENT than the recipe:" \
349 "" \
350 "$( stat -c "%y %n" "${CWD}/post-install" )" \
351 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
352 "" \
353 " This recipe will be re-processed ..." \
355 touch "${CWD}/$recipe"
356 elif test "$opt_skipqsts" = opt_skipqsts
357 then
358 warn "Recipe for '${full_pkgname}.tlz': [Ignored]." ""
359 return 0
360 else
361 warn \
362 "" \
363 "This recipe ALREADY produced a package:" \
364 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
365 "" \
366 "The recipe is still OLDER than the produced package:" \
367 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
368 "" \
369 " Probably nothing has changed." \
372 # In non-interactive mode, the user is asked about
373 # rebuilding the package. In interactive mode,
374 # the user need to pass the option explicitly
375 if test ! -t 0
376 then
377 printf '%s\n' \
378 "Do you want to rebuild this package?" \
379 "1) Yes, built it" \
380 "2) No, skip it [default]" \
381 "3) Resume, skipping completed recipes" \
382 "Enter an option number:" > /dev/tty
383 IFS= read -r ANSWER < /dev/tty || exit 2;
384 case $ANSWER in
386 echo "$ANSWER" > /dev/tty
387 unset -v ANSWER
390 unset -v ANSWER
391 echo "=== Building unprocessed or recently modified recipe(s) ..." > /dev/tty
392 echo ""
393 opt_skipqsts=opt_skipqsts
394 readonly opt_skipqsts
395 return 0
398 unset -v ANSWER
399 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
400 echo ""
401 return 0
403 esac
404 else
405 warn "Use the --force option to reprocess ${CWD}/${recipe}." ""
406 return 6;
411 # Fetch remote sources
413 echo "=== Fetching remote sources if needed ..."
414 if test -n "$fetch"
415 then
416 for origin in $fetch
418 echo "=== Looking for $origin ..."
420 _source="${origin##*/}"; # Get the file name.
422 echo "=== Verifying checksum file found \`${tardir}/${_source}.sha256'"
423 if test -e "${tardir}/${_source}.sha256"
424 then
425 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
426 chkstatus_or_exit
427 continue;
428 else
429 warn " Checksum file \`${_source}.sha256' does not exist"
432 # Download source or resume if allowed
434 if test ! -e "${tardir}/$_source"
435 then
436 warn "=== Attempting to get it from $origin ..."
439 case $origin in
440 rsync://*)
442 cd -- "$tardir" && $rsync "$origin" || exit $?
443 sha256sum "$_source" > "${_source}.sha256"
444 ); chkstatus_or_exit 10
446 *://*)
448 cd -- "$tardir" && $netget "$origin" || exit $?
449 sha256sum "$_source" > "${_source}.sha256"
450 ); chkstatus_or_exit 10
453 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
454 exit 4
455 esac
456 done
457 unset -v origin _source
458 else
459 warn "${recipe}: The variable 'fetch' is empty."
462 # Prepare special directories for build the source,
463 # the destination and the output of the package
465 echo "=== Preparing directories ..."
467 if test -z "$keep_srcdir"
468 then
469 if test -e "${TMPDIR}/$srcdir"
470 then
471 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
472 echo "removed directory: '${TMPDIR}/$srcdir'"
474 else
475 warn \
476 "WARNING: The variable 'keep_srcdir' has been set (${keep_srcdir})."
479 if test -z "$keep_destdir"
480 then
481 if test -e "$destdir"
482 then
483 rm -r -- "$destdir" || chkstatus_or_exit
484 echo "removed directory: '$destdir'"
486 mkdir -p -- "$destdir" || chkstatus_or_exit
487 echo "mkdir: created directory '$destdir'"
490 if test ! -e "$outdir"
491 then
492 mkdir -p -- "$outdir" || chkstatus_or_exit
493 echo "mkdir: created directory '$outdir'"
496 echo "=== Changing to '${TMPDIR}' ..."
497 cd -- "$TMPDIR" || chkstatus_or_exit
499 # Set trap before to run the build() function in order
500 # to catch the return status, exit code 2 if fails
502 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
504 # Determine if the debugging indicators of the shell should be
505 # retained, assuming that it has been previously passed
506 case $- in *x*)
507 _xtrace_flag=_xtrace_flag_is_set ;;
508 esac
510 echo "=== Running the 'build' function ..."
511 build
512 unset -f build
514 # Check recipe to run (extra) defined functions by the packager
515 for _definition in \
516 $(awk '!/^build[ (]/ && /^[^ {}]+ *\(\)/{ gsub(/[()]/, "", $1); print $1 }' "${CWD}/$recipe")
518 # Check if it is a shell function
519 case $(LC_ALL=C type $_definition) in
520 *function*)
521 # Call and undo the function after executing it
522 $_definition
523 unset -f $_definition
525 esac
526 done
527 unset -v _definition
529 # Turn off possible shell flags coming from the recipe
531 set +e
532 if test "${_xtrace_flag:+$_xtrace_flag}" != _xtrace_flag_is_set
533 then
534 set +x
537 # Reset given signals
538 trap - EXIT HUP INT QUIT ABRT TERM
540 # If 'destdir' is empty, the package won't be created
541 if rmdir -- "$destdir" 2> /dev/null
542 then
543 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
544 opt_nopkg=opt_nopkg
547 # Create (make) the package
549 if test "$opt_nopkg" != opt_nopkg
550 then
551 # Edit the recipe when 'release' is incremented
552 if test "$opt_incr_release" = opt_incr_release
553 then
554 echo ",s/^\\(release\\)=.*/\\1=${release}/"$'\nw' | \
555 ed "${CWD}/$recipe" || chkstatus_or_exit
558 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
560 # Include a copy of the recipe into the package
561 cp -p "${CWD}/$recipe" \
562 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
563 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe"
564 chkstatus_or_exit
566 # Produce a checksum file for recipe copy
567 cd -- "${destdir}/var/lib/qi" && \
568 sha256sum "${full_pkgname}.recipe" > "${full_pkgname}.recipe.sha256"
569 ); chkstatus_or_exit 4
571 # Detect post-install script for inclusion
573 if test -f "${CWD}/post-install"
574 then
575 echo "${CWD}/post-install: Detected."
576 cp -p "${CWD}/post-install" \
577 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
578 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh"
579 chkstatus_or_exit
582 # Detect declared package name(s) for later replacement
584 if test -n "$replace"
585 then
586 warn \
587 "=== The following package names has been declared for replacement:" \
588 "$replace"
590 : > "${destdir}/var/lib/qi/${full_pkgname}.replace"
591 for item in $replace
593 printf '%s\n' "$replace" >> \
594 "${destdir}/var/lib/qi/${full_pkgname}.replace"
595 done
596 unset -v item
599 # Create (external) meta file for package information,
600 # make a copy of it for the package database
601 echo "=== Creating meta file ${full_pkgname}.tlz.txt ..."
602 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
603 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
604 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
606 # Produce the package
607 cd -- "$destdir" && mode_create "${outdir}/${full_pkgname}.tlz"
610 # Back to the current working directory
611 cd -- "$CWD" || chkstatus_or_exit
613 echo "=== Deleting 'srcdir' or 'destdir' ..."
614 if test "$opt_keep" != opt_keep
615 then
616 if test -z "$keep_srcdir"
617 then
618 srcdir="${srcdir%%/*}"; # Directory name without parents.
619 if test -e "${TMPDIR}/$srcdir"
620 then
621 rm -rf -- "${TMPDIR:?}/$srcdir" || chkstatus_or_exit
622 echo "removed directory: '${TMPDIR}/$srcdir'"
625 if test -z "$keep_destdir"
626 then
627 if test -e "$destdir"
628 then
629 rm -rf -- "$destdir" || chkstatus_or_exit
630 echo "removed directory: '$destdir'"
633 else
634 warn \
635 " The following directories will be preserved:" \
636 "${TMPDIR}/$srcdir" \
637 "$destdir" \
638 "" \
639 "The '--keep' option has been used."
642 # Install or upgrade the package after build
643 if test "$opt_nopkg" != opt_nopkg
644 then
645 if test "$opt_install" = opt_install
646 then
647 mode_install "${outdir}/${full_pkgname}.tlz"
648 elif test "$opt_upgrade" = opt_upgrade
649 then
650 mode_upgrade "${outdir}/${full_pkgname}.tlz"
654 warn "{@} Recipe \"${CWD}/${recipe}\" has been processed." ""
657 mode_create()
659 directory="$(dirname -- "$1")"
661 # Perform sanity checks
663 if ! fnmatch '/?*' "$directory"
664 then
665 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
666 exit 4
668 is_readable "$directory" || exit 4
670 name="$(basename -- "$1")"
672 echo ""
673 echo "{#} Creating package name \`${name}' ..."
675 if test "$name" = "${name%.tlz}"
676 then
677 warn "Package format '$name' not supported." \
678 "It should be \"name_version_architecture-release[@pkgcategory].tlz\""
679 exit 4
682 # If needed, assign default values for compression options
683 tarlz_compression_options="${tarlz_compression_options:=-9 --solid}"
685 # Pass extra options to tarlz(1)
686 if test -n "$SOURCE_DATE_EPOCH"
687 then
688 tarlz_compression_options="$tarlz_compression_options --mtime=@${SOURCE_DATE_EPOCH}"
691 ( umask 022 ; tarlz $tarlz_compression_options -cvf - -- * ) > "${directory}/$name"
692 chkstatus_or_exit 3
694 ( cd -- "$directory" && sha256sum "$name" > "${name}.sha256" )
695 chkstatus_or_exit 4
697 warn "{#} Package \"${name}\" created on ${directory}." ""
699 # Remove used variables
700 unset -v directory name
703 mode_remove()
705 expunge="${packagedir}/$(basename -- "$1" .tlz)"
707 echo ""
708 echo "{<} Removing \`$rootdir${expunge}' ..."
710 # Complain if the package directory cannot be well-read
712 is_readable "$rootdir${expunge}" || exit 4
714 # Validate package directory as such
716 test -d "$rootdir${expunge}" || {
717 warn "Package '$rootdir${expunge}' is not a valid directory."
718 exit 4
721 # Remove package from Graft control
723 # Scan for possible conflicts, stop if arise
724 if test "$opt_prune" != opt_prune
725 then
726 echo "=== Checking for possible conflicts ..."
727 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
728 grep "^CONFLICT"
729 then
730 warn "" \
731 " A conflict occurred during uninstallation;" \
732 "Unless the --prune option is given, this package will be PRESERVED."
733 exit 6;
737 # Remove objects (files, links or directories) from the target
738 # directory that are in conflict with the package directory
740 echo "=== Pruning any conflict ..."
741 graft -p -D -u $graft_r -t "$targetdir" "$expunge"
742 chkstatus_or_exit 2
744 echo "=== Disabling links ..."
745 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
746 chkstatus_or_exit 2
748 # Delete package directory
749 if test "$opt_keep" != opt_keep
750 then
751 echo "=== Deleting package directory ..."
752 if is_readable "${rootdir}$expunge"
753 then
754 rm -r -- "${rootdir}$expunge" || chkstatus_or_exit
755 echo "removed directory: '${rootdir}$expunge'"
759 warn "{<} Package \"${expunge##*/}\" removed from $rootdir${expunge%%/*}." ""
760 unset -v expunge
763 mode_install()
765 # Complain if the package cannot be well-read
767 is_readable "$1" || exit 4
769 # Complain if the package does not end in .tlz
771 if ! fnmatch '*.tlz' "$1"
772 then
773 warn "\`${1}' does not end in .tlz"
774 exit 4
777 # Make preparations to install the package
779 echo ""
780 echo "{>} Installing $1 ..."
782 echo "=== Checking tarball integrity ..."
783 tarlz --missing-crc -tf "$1" > /dev/null
784 chkstatus_or_exit 3
786 # To accept random directory from the upgrade mode
787 _packagedir="$2"
788 _packagedir="${_packagedir:=$packagedir}"
790 # Create package directory using 'name'
792 name="$(basename -- "$1" .tlz)"; # Get the file name.
794 if ! test -d "$rootdir${_packagedir}/$name"
795 then
796 mkdir -p -- "$rootdir${_packagedir}/$name" || chkstatus_or_exit
797 echo "mkdir: created directory '$rootdir${_packagedir}/$name'"
800 # Scan for possible conflicts, stop if arise
801 if test "$opt_prune" != opt_prune
802 then
803 echo "=== Checking for possible conflicts ..."
804 if graft -i -n $graft_r -t "$targetdir" "${_packagedir}/$name" 2>&1 | \
805 grep "^CONFLICT"
806 then
807 warn "" \
808 " A conflict occurred during installation;" \
809 "Unless the --prune option is given, this package won't be LINKED."
810 exit 6;
814 echo "=== Decompressing package ..."
815 ( cd -- "$rootdir${_packagedir}/$name" && tarlz -xpf - ) < "$1"
816 chkstatus_or_exit 3
818 # Transite package to Graft control
820 # Remove objects (files, links or directories) from the target
821 # directory that are in conflict with the package directory
822 echo "=== Pruning any conflict ..."
823 graft -p -D -u $graft_r -t "$targetdir" "${_packagedir}/$name"
824 chkstatus_or_exit 2
826 echo "=== Enabling symbolic links ..."
827 graft -i -P $graft_v $graft_r -t "$targetdir" "${_packagedir}/$name"
828 chkstatus_or_exit 2
830 # Avoid unnecessary runs coming from the upgrade mode,
831 # this is when the incoming package is **pre-installed**
833 if test "$_isUpgrade" != _isUpgrade.on
834 then
835 # Show package description
836 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
837 then
838 awk '/^#/' "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.txt"
839 elif test -r "${1}.txt"
840 then
841 # From external meta file (current directory)
842 awk '/^#/' "${1}.txt"
843 else
844 warn "Description file not found for '$name'."
847 # Check and run the post-install script if exist
848 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
849 then
850 echo "=== Running post-install script for \`${name}' ..."
852 # Rely on 'targetdir' if 'rootdir' is empty
853 cd -- "${rootdir:=$targetdir}"/ && \
854 . "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.sh"
858 # Check if there are declared packages for replacement
859 if test -r "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
860 then
861 while read -r line
863 for replace in "$rootdir${_packagedir}/${line%%_*}"_*
865 if ! test -e "$replace"
866 then
867 warn " Declared package \`${replace}' to be replaced does not exist. [Ignored]"
868 continue;
871 replace="${replace##*/}"
873 # The search for the package to be replaced cannot
874 # be the same to the incoming package, even to the
875 # temporary location coming from the upgrade mode
876 if test "$replace" = "$name" || \
877 test "_x_${replace}" = "_x_${PRVLOC##*/}"
878 then
879 continue;
882 warn "WARNING: Replacing package \`${replace}' ..."
884 # Since the links belongs to the new package, only
885 # those which are not in conflict can be deleted.
886 # To complete, we will remove the package directory
888 graft -d -D -u $graft_r \
889 -t "$targetdir" "$replace" > /dev/null 2>&1
891 rm -rf -- "$rootdir${_packagedir}/$replace"
892 done
893 done < "$rootdir${_packagedir}/${name}/var/lib/qi/${name}.replace"
897 warn "{>} Package \"${name}\" installed on $rootdir${_packagedir}." ""
898 unset -v name _packagedir; # Remove used variables.
901 mode_order()
903 # Complain if the file cannot be well-read
905 is_readable "$1" || exit 4
907 # Complain if the file does not end in .order
909 if ! fnmatch '*.order' "$1"
910 then
911 warn "\`${1}' does not end in .order"
912 exit 4
915 # Get a clean list of the file while printing its contents in
916 # reverse order. Last awk(1) in the pipeline eliminates
917 # non-consecutive lines: duplicates, blank lines, and colons.
918 # Comment lines beginning with '#' are allowed; parentheses
919 # are simply ignored
921 awk '{
922 gsub( /:|^#(.*)$|\([^)]*)|^$/,"" );
923 for( i=NF; i > 0; i-- ) print $i
924 }' "$1" | awk '!s[$0]++'
927 mode_upgrade()
929 # Complain if the package does not end in .tlz
931 if ! fnmatch '*.tlz' "$1"
932 then
933 warn "\`${1}' does not end in .tlz"
934 exit 4
937 echo ""
938 echo "{^} Upgrading $1 ..."
940 incoming="$(basename -- "$1" .tlz)"; # Get the file name.
942 # Check package pre-existence
943 if test "$opt_force" != opt_force && \
944 test -e "$rootdir${packagedir}/$incoming"
945 then
946 warn "" \
947 " The package to be upgraded already exist;" \
948 "Unless the --force option is given, this package won't be UPGRADED."
949 exit 6;
952 # Ignore some signals until the upgrade process is complete
953 trap "" HUP INT QUIT ABRT TERM
955 # Check blacklisted packages before to proceed with the upgrade
957 echo "=== Checking blacklist ..."
958 for item in $blacklist
960 case $item in
961 "${incoming%@*}"*)
962 warn \
963 " A blacklisted package name has been detected:" \
964 "$incoming" \
965 "" \
966 "^ This package will be INSTALLED instead of being upgraded ..."
967 opt_prune=opt_prune mode_install "$1"
968 return 0;
970 esac
971 done
972 unset -v item
974 # Prepare the package to install it in a temporary but custom location
976 # Set random directory under 'rootdir/packagedir' using 'incoming' as name
977 PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
979 echo "=== Pre-installing package in temporary location ..."
981 opt_prune=opt_prune; # Turns on prune operation.
983 _isUpgrade=_isUpgrade.on mode_install "$1" "$PRVLOC" > /dev/null
984 _isUpgrade=_isUpgrade.off
986 echo "=== Looking for installations under the same name ..."
987 for previous_package in "$rootdir${packagedir}/${incoming%%_*}"_*
989 test -e "$previous_package" || continue
991 # Previous package directory to be removed cannot be
992 # the same to the temporary (private) location
993 if test "$previous_package" = "$PRVLOC"
994 then
995 continue;
998 # The directory of the package could be preserved if --keep
999 mode_remove "$previous_package"
1000 done
1001 unset -v previous_package
1003 # Re-install incoming package to the final location
1005 mode_install "$1"
1006 opt_prune=opt_prune.off; # Turns off prune operation.
1008 echo "=== Deleting temporary location ..."
1009 if is_readable "$PRVLOC"
1010 then
1011 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1012 echo "removed directory: '$PRVLOC'"
1014 unset -v PRVLOC
1016 warn "{^} Package \"${incoming}\" upgraded for $rootdir${packagedir}." ""
1017 unset -v incoming
1019 # Reset given signals
1020 trap - HUP INT QUIT ABRT TERM
1023 mode_warn()
1025 # Complain if the package cannot be well-read
1027 is_readable "$1" || exit 4
1029 # Complain if the package does not end in .tlz
1031 if ! fnmatch '*.tlz' "$1"
1032 then
1033 warn "\`${1}' does not end in .tlz"
1034 exit 4
1037 # List content of files excluding directories
1038 while test -f "$1"
1040 tarlz -tvvf "$1" | awk '!/^drwx/'
1041 chkstatus_or_exit 3
1042 shift;
1043 done
1044 return 0
1047 mode_extract()
1049 # Perform sanity checks before package extraction
1051 is_readable "$1" || exit 4
1053 test -f "$1" || {
1054 warn "\`${1}' is not a regular file."
1055 exit 4
1058 # Preparations to extract the package
1060 name="$(basename -- "$1" .tlz)"
1062 # Set random directory under 'TMPDIR' using 'name'
1063 PRVDIR=$(mktemp -dp "$TMPDIR" ${name}.XXXXXXXXXXXX) || exit 2
1065 # Trap to remove 'PRVDIR' on disruptions
1066 trap 'rm -rf -- "$PRVDIR"' HUP INT ABRT TERM
1068 # Create 'PRVDIR' removing access for all but user
1069 ( umask 077 ; mkdir -- "$PRVDIR" )
1070 mkdir -p -- "$PRVDIR"
1071 chmod 700 -- "$PRVDIR"
1073 echo ""
1074 echo "{-} Extracting package \`${name}' ..."
1076 ( umask 000 ; cd -- "$PRVDIR" && tarlz -xvf - ) < "$1"
1077 if test $? -ne 0
1078 then
1079 # Try to remove (empty) 'PRVDIR' on failure
1080 rmdir -- "$PRVDIR"
1081 exit 3;
1084 warn "" "{-} Package \"${name}\" has been extracted on ${PRVDIR}."
1086 # Remove used variables
1087 unset -v name PRVDIR
1089 # Reset given signals
1090 trap - HUP INT ABRT TERM
1093 ### Extra functions to be used during the modes
1095 unpack()
1097 for file in "$@"
1099 case $file in
1100 *.tar)
1101 tar -tf "$file" > /dev/null && \
1102 tar -xpf "$file"
1103 chkstatus_or_exit 3
1105 *.tar.gz | *.tgz | *.tar.Z )
1106 gzip -cd "$file" | tar -tf - > /dev/null && \
1107 gzip -cd "$file" | tar -xpf -
1108 chkstatus_or_exit 3
1110 *.tar.bz2 | *.tbz2 | *.tbz )
1111 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1112 bzip2 -cd "$file" | tar -xpf -
1113 chkstatus_or_exit 3
1115 *.tar.lz | *.tlz )
1116 lzip -cd "$file" | tar -tf - > /dev/null && \
1117 lzip -cd "$file" | tar -xpf -
1118 chkstatus_or_exit 3
1120 *.tar.xz | *.txz )
1121 xz -cd "$file" | tar -tf - > /dev/null && \
1122 xz -cd "$file" | tar -xpf -
1123 chkstatus_or_exit 3
1125 *.zip | *.ZIP )
1126 unzip -t "$file" > /dev/null && \
1127 unzip "$file" > /dev/null
1128 chkstatus_or_exit 3
1130 *.gz)
1131 gzip -t "$file" && \
1132 gzip -cd "$file" > "$(basename -- "$file" .gz)"
1133 chkstatus_or_exit 3
1135 *.Z)
1136 gzip -t "$file" && \
1137 gzip -cd "$file" > "$(basename -- "$file" .Z)"
1138 chkstatus_or_exit 3
1140 *.bz2)
1141 bzip2 -t "$file" && \
1142 bzip2 -cd "$file" > "$(basename -- "$file" .bz2)"
1143 chkstatus_or_exit 3
1145 *.lz)
1146 lzip -t "$file" && \
1147 lzip -cd "$file" > "$(basename -- "$file" .lz)"
1148 chkstatus_or_exit 3
1150 *.xz)
1151 xz -t "$file" && \
1152 xz -cd "$file" > "$(basename -- "$file" .xz)"
1153 chkstatus_or_exit 3
1156 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1157 exit 1
1158 esac
1159 done
1160 unset -v file
1163 do_meta()
1165 # Extract information from the recipe to create the meta file.
1167 # The package description is pre-formatted in 78 columns,
1168 # the '#' character and a space is added as prefix to conform
1169 # the 80 columns in total
1170 printf '%s\n' "$description" | fold -w 78 | awk '$0="# " $0'
1172 # Include build flags only if it is a real architecture
1173 if test "$arch" != noarch
1174 then
1175 printf '%s' \
1177 QICFLAGS=\"$QICFLAGS\"
1178 QICXXFLAGS=\"$QICXXFLAGS\"
1179 QILDFLAGS=\"$QILDFLAGS\"
1180 QICPPFLAGS=\"$QICPPFLAGS\""
1183 # Print saving the rest of the package information
1184 printf '%s' \
1186 pkgname=$pkgname
1187 pkgversion=$pkgversion
1188 arch=$arch
1189 release=$release
1190 pkgcategory=\"${pkgcategory#@*}\"
1191 full_pkgname=$full_pkgname
1192 blurb=\"$(printf '%s\n' "$description" | sed -e '/^$/d;2q')\"
1193 homepage=\"$homepage\"
1194 license=\"$license\"
1195 fetch=\"$fetch\"
1196 replace=\"$replace\"
1200 ### Default values
1202 packagedir=@PACKAGEDIR@
1203 targetdir=@TARGETDIR@
1204 blacklist="perl5 graft tarlz plzip musl glibc coreutils bash mksh"
1205 _rcfile=@SYSCONFDIR@/qirc
1206 opt_install=opt_install.off
1207 opt_upgrade=opt_upgrade.off
1208 opt_force=opt_force.off
1209 opt_keep=opt_keep.off
1210 opt_incr_release=opt_incr_release.off
1211 opt_skipqsts=opt_skipqsts.off
1212 opt_nopkg=opt_nopkg.off
1213 opt_prune=opt_prune.off
1214 verbose_level=0
1215 rootdir=""
1216 arch=@ARCH@
1217 jobs=1
1218 mode=""
1219 _xtrace_flag=""
1220 _readstdin=""
1221 graft_v=""
1222 graft_r=""
1223 _isUpgrade=_isUpgrade.off
1224 keep_srcdir=""
1225 keep_destdir=""
1226 TMPDIR="${TMPDIR:-/usr/src/qi/build}"
1227 QICFLAGS="${QICFLAGS:--O2}"
1228 QICXXFLAGS="${QICXXFLAGS:--O2}"
1229 QILDFLAGS="${QILDFLAGS:-}"
1230 QICPPFLAGS="${QICPPFLAGS:-}"
1231 worktree=/usr/src/qi
1232 tardir=${worktree}/sources
1233 outdir=/var/cache/qi/packages
1234 netget="wget2 -c -w1 -t3 --no-check-certificate"
1235 rsync="rsync -v -a -L -z -i --progress"
1236 tarlz_compression_options="-9 --solid"
1237 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1238 infodir=@INFODIR@
1239 mandir=@MANDIR@
1240 docdir=@DOCDIR@
1242 # Store (default) directory locations
1243 QI_TARGETDIR=$targetdir
1244 QI_PACKAGEDIR=$packagedir
1245 QI_WORKTREE=$worktree
1246 QI_TARDIR=$tardir
1247 QI_OUTDIR=$outdir
1249 ### Parse commands and options
1251 validate_mode()
1253 if test -n "$mode"
1254 then
1255 warn "${PROGRAM}: First defined command: ${mode#*_}" \
1256 "Switching to another command is not allowed (${1})."
1257 exit 1
1261 validate_option()
1263 if test -z "$2"
1264 then
1265 warn "${PROGRAM}: The '${1}' option requires an argument" \
1266 "Try '${PROGRAM} --help' for more information."
1267 exit 1
1271 validate_directory()
1273 if test ! -d "$2"
1274 then
1275 warn "${PROGRAM}: ${1} \"${2}\" must be a valid directory name"
1276 exit 1
1280 validate_digit()
1282 name="$1"
1283 shift
1285 # Taken from https://mywiki.wooledge.org/BashFAQ/054
1286 case "${1#[-+]}" in
1288 warn "${PROGRAM}: The '${name}' option has no defined value"
1289 exit 1
1291 *.*.*)
1292 warn "${PROGRAM}: The '${name}' option has more than one decimal point on it \"${1}\""
1293 exit 1
1295 *[!0-9]*)
1296 warn "${PROGRAM}: The '${name}' option contains a non-valid digit on it \"${1}\""
1297 exit 1
1299 esac
1301 unset -v name
1304 while test $# -gt 0
1306 case $1 in
1307 warn)
1308 validate_mode warn
1309 mode=mode_warn
1311 install)
1312 validate_mode install
1313 readconfig
1314 mode=mode_install
1316 remove)
1317 validate_mode remove
1318 readconfig
1319 mode=mode_remove
1321 upgrade)
1322 validate_mode upgrade
1323 readconfig
1324 mode=mode_upgrade
1326 extract)
1327 validate_mode extract
1328 mode=mode_extract
1330 create)
1331 validate_mode create
1332 mode=mode_create
1334 order)
1335 validate_mode order
1336 mode=mode_order
1338 build)
1339 validate_mode build
1340 readconfig
1341 mode=mode_build
1343 --no-rc | -N )
1344 _readconfig=readconfig.off
1346 --install | -i )
1347 opt_install=opt_install
1349 --upgrade | -u )
1350 opt_upgrade=opt_upgrade
1352 --force | -f )
1353 opt_force=opt_force
1355 --keep | -k )
1356 opt_keep=opt_keep
1358 --prune | -p )
1359 opt_prune=opt_prune
1361 --packagedir | -P )
1362 validate_option "$1" "$2"
1363 packagedir="$2"
1364 validate_directory "$1" "$packagedir"
1365 shift
1367 --packagedir=*)
1368 validate_option "$1" "$2"
1369 packagedir="${1#*=}"
1370 validate_directory "$1" "$packagedir"
1372 --targetdir | -t )
1373 validate_option "$1" "$2"
1374 targetdir="$2"
1375 validate_directory "$1" "$targetdir"
1376 shift
1378 --targetdir=*)
1379 validate_option "$1" "$2"
1380 targetdir="${1#*=}"
1381 validate_directory "$1" "$targetdir"
1383 --rootdir | -r )
1384 validate_option "$1" "$2"
1385 rootdir="$2"
1386 validate_directory "$1" "$rootdir"
1387 shift
1389 --rootdir=*)
1390 validate_option "$1" "$2"
1391 rootdir="${1#*=}"
1392 validate_directory "$1" "$rootdir"
1394 --outdir | -o )
1395 validate_option "$1" "$2"
1396 outdir="$2"
1397 validate_directory "$1" "$outdir"
1398 shift
1400 --outdir=*)
1401 validate_option "$1" "$2"
1402 outdir="${1#*=}"
1403 validate_directory "$1" "$outdir"
1405 --worktree | -w )
1406 validate_option "$1" "$2"
1407 worktree="$2"
1408 validate_directory "$1" "$worktree"
1409 shift
1411 --worktree=*)
1412 validate_option "$1" "$2"
1413 worktree="${1#*=}"
1414 validate_directory "$1" "$worktree"
1416 --sourcedir | -s )
1417 validate_option "$1" "$2"
1418 tardir="$2"
1419 validate_directory "$1" "$tardir"
1420 shift
1422 --sourcedir=*)
1423 validate_option "$1" "$2"
1424 tardir="${1#*=}"
1425 validate_directory "$1" "$tardir"
1427 --architecture | -a )
1428 validate_option "$1" "$2"
1429 arch="$2"
1430 shift
1432 --arch=*)
1433 validate_option "$1" "$2"
1434 arch="${1#*=}"
1436 --jobs | -j )
1437 jobs="$2"
1438 validate_digit "$1" "$jobs"
1439 shift
1441 -j[0-9]*)
1442 jobs="${1#-j*}"
1443 validate_digit '-j' "$jobs"
1445 --jobs=*)
1446 jobs="${1#*=}"
1447 validate_digit '--jobs=' "$jobs"
1449 --no-package | -n )
1450 opt_nopkg=opt_nopkg
1452 --increment | -1 )
1453 opt_incr_release=opt_incr_release
1455 --skip-questions | -S )
1456 opt_skipqsts=opt_skipqsts
1458 --verbose | -v )
1459 verbose_level=$(( verbose_level + 1 ))
1461 -vv)
1462 # A trick for a second -v.
1463 verbose_level=2
1465 --show-location | -L )
1466 printf '%s\n' \
1467 "QI_TARGETDIR=$QI_TARGETDIR" \
1468 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1469 "QI_WORKTREE=$QI_WORKTREE" \
1470 "QI_TARDIR=$QI_TARDIR" \
1471 "QI_OUTDIR=$QI_OUTDIR"
1472 exit 0
1474 --help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' | \
1475 help )
1476 usage
1477 exit 0
1479 --version | --versio | --versi | --vers | \
1480 -version | -versio | -versi | -vers | -V | version )
1481 echo "$PROGRAM version @VERSION@"
1482 exit 0
1484 '-')
1485 _readstdin=readstdin
1486 break
1489 shift
1490 break; # End of options.
1493 warn "qi: Unrecognized option: $1" \
1494 "Try '${PROGRAM} --help' for more information."
1495 exit 1
1498 break; # No more options.
1500 esac
1501 shift
1502 done
1503 unset -f readconfig validate_mode validate_option validate_directory validate_digit
1505 # When there are no arguments, show the help
1506 if test $# -eq 0
1507 then
1508 usage
1509 exit 1
1511 unset -f usage
1513 # Program sanity check
1515 for need in awk basename chmod cp dirname find fold graft grep \
1516 mkdir mktemp rm rmdir sed sha256sum stat tarlz ; \
1518 if ! \command -v $need > /dev/null
1519 then
1520 warn "${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1521 exit 2
1523 done
1524 unset -v need
1526 # Set verbosity level and flags
1528 if test "$verbose_level" -gt 0
1529 then
1530 if test "$verbose_level" -eq 1
1531 then
1532 graft_v=-v
1533 else
1534 graft_v=-V
1538 # Read standard input if FILE is -, or when
1539 # FILE is not connected to a terminal
1541 if test "$_readstdin" = readstdin
1542 then
1543 if test -t 0
1544 then
1545 warn "qi: I won't read from a connected terminal." \
1546 "Try '${PROGRAM} --help' for more information."
1547 exit 1
1550 # Unset positional parameters setting $# to zero
1551 set --
1553 # Assign remaining arguments to the positional parameters
1554 while read -r input
1556 set -- "$@" "$input"
1557 done
1559 unset -v _readstdin
1561 if test -z "$mode"
1562 then
1563 warn "qi: We need at least one (valid) command." \
1564 "Try '${PROGRAM} --help' for more information."
1565 exit 4
1568 # Validate 'packagedir' and 'targetdir' as canonical directories
1570 # The single slash '/' does not qualify here
1571 if ! fnmatch '/?*' "$packagedir"
1572 then
1573 warn "${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1574 exit 4
1576 if test ! -d "$packagedir"
1577 then
1578 warn "${PROGRAM}: Package directory \`${packagedir}' does not exist"
1579 exit 4
1582 # The single slash '/' is valid here
1583 if ! fnmatch '/*' "$targetdir"
1584 then
1585 warn "${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1586 exit 4
1588 if test ! -d "$targetdir"
1589 then
1590 warn "${PROGRAM}: Target directory \`${targetdir}' does not exist"
1591 exit 4
1594 # Validate 'rootdir' directory
1596 if test -n "$rootdir"
1597 then
1598 if test -d "$rootdir" && test "$rootdir" != /
1599 then
1600 rootdir="${rootdir%/}" # Remove slash from the end.
1602 # A workaround for graft-2.13+. The specified directory is
1603 # relative to the log file, we prepend it inside 'rootdir'
1605 eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
1606 mkdir -p -- "$rootdir$(dirname -- "$GRAFT_LOGFILE")" || chkstatus_or_exit
1608 # Compose 'rootdir' and log file option to be used with graft(1)
1609 graft_r="-r $rootdir -l $GRAFT_LOGFILE"
1611 # Unset variables coming from eval
1612 unset -v GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1613 else
1614 warn "${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1615 exit 4
1617 readonly rootdir
1618 export rootdir
1621 # Ensure 'TMPDIR' creation to prefix temporary files
1623 if test ! -d "$TMPDIR"
1624 then
1625 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1627 readonly TMPDIR packagedir targetdir
1629 # Process each package or recipe provided on the command-line
1631 for package in "$@"
1633 $mode "$package"
1634 done