qi: do not use tarlz for decompress .tar.lz files when unpack()
[dragora.git] / qi / src / qi.in
blob9a7656a1161fcf03a321c5768a1074024bbbaa15
1 #! /bin/sh -
2 # Copyright (C) 2016-2019 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 #### Functions
29 usage()
31 printf "%s\n" \
32 "Qi - A user-friendly package manager." \
33 "" \
34 "Usage: $PROGRAM [OPTION...] [FILE]..." \
35 "" \
36 "Operation mode:" \
37 " -b Build packages using recipe names" \
38 " -c Create .tlz package from directory" \
39 " -d Delete packages" \
40 " -i Install packages" \
41 " -o Resolve build order through .order files" \
42 " -u Update packages (implies -i, -d with -p)" \
43 " -w Warn about files that will be linked" \
44 " -x Extract a package for debugging purposes" \
45 "" \
46 "Common options:" \
47 " -N Do not read the configuration file" \
48 " -P <DIR> Package directory for installations." \
49 " Only valid for -i, -d, or -u options" \
50 " -f This option can force the build of a recipe," \
51 " or force the update of a pre-existing package." \
52 " Only valid for -b, -u options" \
53 " -t <DIR> Target directory for symbolic links." \
54 " Only valid for -i, -d, or -u options" \
55 " -k Keep \`\${srcdir}' or \`\${destdir}' in build mode," \
56 " keep package directory in delete mode." \
57 " Only valid for -b, -d or -u options" \
58 " -p Prune conflicts on package installations" \
59 " -r <DIR> Use the fully qualified named directory as the" \
60 " root directory for all qi operations. The target" \
61 " directory and package directory will be relative to" \
62 " the specified directory, including the log file for" \
63 " graft" \
64 " -v Be verbose (a 2nd -v gives more)" \
65 "" \
66 "Options for 'build' mode (-b):" \
67 " -O <DIR> Where the packages produced are written" \
68 " -W <DIR> Where archives, patches, and recipes are expected" \
69 " -Z <DIR> Where (compressed) sources will be found" \
70 " -a Architecture to use [detected]" \
71 " -j Parallel jobs for the compiler" \
72 " -1 Increment release number (\`\${release}' + 1)" \
73 " -n Don't create a .tlz package" \
74 " -S Selects the option to skip completed recipes" \
75 "" \
76 "Informative options:" \
77 " -L Print default directory locations" \
78 " -h Display this help and exit" \
79 " -V Output version information" \
80 "" \
81 "Some influential environment variables:" \
82 " TMPDIR Temporary directory for sources" \
83 " QICFLAGS C compiler flags" \
84 " QICXXFLAGS C++ compiler flags" \
85 " QILDFLAGS Linker flags" \
86 "" \
87 "When FILE is -, read standard input." \
91 version()
93 printf "%s\n" \
94 "$PROGRAM @VERSION@" \
95 "Copyright (C) 2016-2019 Matias Andres Fonzo." \
96 "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
97 "This is free software: you are free to change and redistribute it." \
98 "There is NO WARRANTY, to the extent permitted by law."
101 warn()
103 printf "%s\n" "$@" 1>&2
106 is_readable()
108 if test -e "$1"
109 then
110 if ! test -r "$1"
111 then
112 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
113 return 1
115 else
116 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
117 return 1
121 # Portable alternative to the file operator -nt (among shells)
122 is_newer()
124 if test -n "$(find $1 -prune -newer $2 -print)"
125 then
126 return 0
128 return 1
131 # Determine whether $2 matches pattern $1
132 fnmatch()
134 case $2 in
136 return 0
139 return 1
141 esac
144 chkstatus_or_exit()
146 status=$?
148 if test $status -ne 0
149 then
150 echo "Return status = $status" 1>&2
151 exit ${1-2}; # If not given, defaults to 2
154 unset status
157 readconfig()
159 if test $RC = RC
160 then
161 is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
163 echo "Processing \`${RCFILE}' ..."
165 test -f "$RCFILE" || {
166 warn "${RCFILE} is not a regular file."
167 return 1
170 # Parse config file
171 while IFS='=' read -r variable value
173 case $variable in
174 \#* | "") # Ignore commented or blank lines
175 continue
177 esac
179 # Set variable name avoiding code execution
180 eval "$variable=\${value}"
181 done < "$RCFILE"
185 #### Mode functions
187 build_mode()
189 recipe=$1
191 # A recipe is any valid regular file. Qi sets priorities for reading a
192 # recipe, the order in which qi looks for a recipe is:
194 # 1. Current working directory.
196 # 2. If the specified path name does not contain "recipe" as the last
197 # component. Qi will complete it by adding "recipe" to the path
198 # name.
200 # 3. If the recipe is not in the current working directory, it will be
201 # searched under '${worktree}/recipes'. The last component will be
202 # completed adding "recipe" to the specified path name.
204 if test ! -f "$recipe"
205 then
206 if test -f "${recipe}/recipe"
207 then
208 recipe="${recipe}/recipe"
209 elif test -f "${worktree}/recipes/${recipe}/recipe"
210 then
211 recipe="${worktree}/recipes/${recipe}/recipe"
215 test -f "$recipe" || {
216 warn "\`${recipe}' is not a regular file."
217 return 4
220 # Complain if the file name is not "recipe"
222 if test "${recipe##*/}" != recipe
223 then
224 warn "\`${recipe}' is not a valid recipe name."
225 return 4
228 # Start preparations to import the recipe
230 # Separate the directory name from the file name,
231 # getting its absolute path and base name
233 CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
234 recipe=$(basename -- "$recipe")
236 # Check readability for load the recipe on success
238 is_readable "${CWD}/$recipe" || exit 4
240 # Find target architecture if 'arch' is not set
241 if test -z "$arch"
242 then
243 arch=$(${CC:-cc} -dumpmachine 2> /dev/null) || arch=unknown
244 arch="${arch%%-*}" # Get the rid of target triplet.
247 # Re-create external directories
248 mkdir -p -- "${worktree}/archive" \
249 "${worktree}/patches" \
250 "${worktree}/recipes" \
251 "$tardir"
253 # Variables treatment for the current and next recipe.
255 # Unset special variables that can only be predefined on
256 # the recipe and not coming from ${sysconfdir}/qirc
258 unset srcdir destdir pkgname pkgversion program version release \
259 fetch description homepage license replace full_pkgname \
260 CFLAGS CXXFLAGS LDFLAGS
262 # The following variables must be saved and restored
263 save_arch="${save_arch:=$arch}"
264 save_jobs="${save_jobs:=$jobs}"
265 save_outdir="${save_outdir:=$outdir}"
266 save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
268 # Reset variable values in case of return
269 arch=$save_arch
270 jobs=$save_jobs
271 outdir=$save_outdir
272 opt_nopkg=$save_opt_nopkg
274 # The following variables cannot be redefined on the recipe
275 readonly worktree netget rsync
277 # Import the recipe
279 echo "{@} Building from ${CWD}/$recipe ..."
280 . "${CWD}/$recipe"
282 # Check for required variables
283 if test -z "$program"
284 then
285 warn "${recipe}: The variable 'program' is not defined."
286 exit 5
288 if test -z "$version"
289 then
290 warn "${recipe}: The variable 'version' is not defined."
291 exit 5
293 if test -z "$release"
294 then
295 warn "${recipe}: The variable 'release' is not defined."
296 exit 5
299 # Pre-settings before to start building
301 # Increment the release number if the option was given
302 if test "$opt_incr_release" = opt_incr_release
303 then
304 release=$(( release + 1 ))
307 # Allow the dot as definition for 'tardir'
308 if test "$tardir" = .
309 then
310 tardir="$CWD"
313 # Set default values for the following special variables
315 pkgname="${pkgname:=$program}"
316 pkgversion="${pkgversion:=$version}"
317 srcdir="${srcdir:=${program}-$version}"
318 destdir="${destdir:=${TMPDIR}/package-$pkgname}"
320 # Complete package name adding 'pkgversion-arch+release'
321 full_pkgname="${full_pkgname:=$pkgname-${pkgversion}-${arch}+${release}}"
323 # If a package is going to be created, the existence of a
324 # previous build will be detected and reported. Under normal
325 # conditions the recipe is built as long as it is newer than
326 # the produced package, if not, we warn to the user about it.
327 # Rebuilding the package is possible (through the force ;-)
329 if test "$opt_nopkg" != opt_nopkg && \
330 { test "$opt_force" != opt_force && \
331 test -e "${outdir}/${full_pkgname}.tlz" ; }
332 then
333 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
334 then
335 warn \
336 "" \
337 "The recipe is more RECENT than the detected package:" \
338 "" \
339 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
340 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
341 "" \
342 " This recipe will be processed ..." \
344 elif test -e "${CWD}/post-install" && \
345 is_newer "${CWD}/post-install" "${CWD}/$recipe"
346 then
347 warn \
348 "" \
349 "The post-install script is more RECENT than the recipe:" \
350 "" \
351 "$( stat -c "%y %n" "${CWD}/post-install" )" \
352 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
353 "" \
354 " The recipe will be re-processed ..." \
356 touch "${CWD}/$recipe"
357 elif test "$opt_skipqsts" = opt_skipqsts
358 then
359 warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
360 return 0
361 else
362 warn \
363 "" \
364 "This recipe ALREADY produced a package:" \
365 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
366 "" \
367 "The recipe is still OLDER than the produced package:" \
368 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
369 "" \
370 " Probably nothing has changed." \
373 # In non-interactive mode, the user is asked about
374 # rebuilding the package. In interactive mode,
375 # the user need to pass the option explicitly
376 if test ! -t 0
377 then
378 printf "%s\n" \
379 "Do you want to rebuild this package?" \
380 "1) Yes, built it" \
381 "2) No, skip it [default]" \
382 "3) Resume, skipping completed recipes" \
383 "Enter an option number:" > /dev/tty
384 IFS= read -r ANSWER < /dev/tty || exit 2;
385 case $ANSWER in
387 echo "$ANSWER" > /dev/tty
388 unset ANSWER
391 unset ANSWER
392 echo "Building UNPROCESSED/MODIFIED recipes ..." > /dev/tty
393 echo ""
394 opt_skipqsts=opt_skipqsts
395 readonly opt_skipqsts
396 return 0
399 unset ANSWER
400 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
401 echo ""
402 return 0
404 esac
405 else
406 warn "Use the -f option to reprocess ${CWD}/$recipe." ""
407 return 6;
412 # Fetch remote sources
414 echo "Fetching remote sources if needed ..."
415 if test -n "$fetch"
416 then
417 for origin in $fetch
419 _source="${origin##*/}"
421 echo "Looking for \"$_source\" ..."
423 echo "Verifying checksum file \"${_source}.sha256\" from '${tardir}'"
424 if test -e "${tardir}/${_source}.sha256"
425 then
426 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
427 chkstatus_or_exit
428 continue;
429 else
430 warn "${_source}.sha256: Checksum file does not exist, yet."
433 # Download source or resume if allowed
435 if test ! -e "${tardir}/$_source"
436 then
437 warn " Can't find $_source in ${tardir};" \
438 "attempting to get it from ${origin%/*} ..."
441 case $origin in
442 rsync://*)
444 cd -- "$tardir" && $rsync $origin || exit $?
445 sha256sum $_source > ${_source}.sha256
446 ); chkstatus_or_exit 10
448 *://*)
450 cd -- "$tardir" && $netget $origin || exit $?
451 sha256sum $_source > ${_source}.sha256
452 ); chkstatus_or_exit 10
455 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
456 exit 4
457 esac
458 done
459 unset origin _source
460 else
461 warn "The variable 'fetch' is empty."
464 # Prepare special directories for build the source,
465 # the destination and the output of the package
467 echo "Preparing directories ..."
469 if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
470 then
471 rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
472 echo "removed directory: '${TMPDIR}/$srcdir'"
475 if test -d "$destdir"
476 then
477 rm -rf -- "$destdir" || chkstatus_or_exit
478 echo "removed directory: '$destdir'"
480 mkdir -p -- "$destdir" || chkstatus_or_exit
481 echo "mkdir: created directory '$destdir'"
483 if test ! -d "$outdir"
484 then
485 mkdir -p -- "$outdir" || chkstatus_or_exit
486 echo "mkdir: created directory '$outdir'"
489 echo "Entering to 'TMPDIR': $TMPDIR ..."
490 cd -- "$TMPDIR" || chkstatus_or_exit
492 # Set trap before to run the build() function in order
493 # to catch the return status, exit code 2 if fails
495 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
497 # Determine if the debugging indicators of the shell should be
498 # retained, assuming that it has been previously passed
499 case $- in *x*)
500 _xtrace_flag_is_set=xtrace_flag_is_set ;;
501 esac
503 echo "Running build() ..."
504 build
505 unset build
507 # Turn off possible shell flags coming from the recipe
509 set +e
510 if test "$_xtrace_flag_is_set" != xtrace_flag_is_set
511 then
512 set +x
515 # Reset given signals
516 trap - EXIT HUP INT QUIT ABRT TERM
518 # If 'destdir' is empty, the package won't be created
519 if rmdir -- "$destdir" 2> /dev/null
520 then
521 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
522 opt_nopkg=opt_nopkg
525 # Create (make) the package
527 if test "$opt_nopkg" != opt_nopkg
528 then
529 # Edit the recipe when 'release' is incremented
530 if test "$opt_incr_release" = opt_incr_release
531 then
532 echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
533 ed "${CWD}/$recipe" || chkstatus_or_exit
536 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
538 # Include a recipe copy into the package
539 cp -p "${CWD}/$recipe" \
540 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
541 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
543 # Detect post-install script for inclusion
545 if test -f "${CWD}/post-install"
546 then
547 echo "${CWD}/post-install: Detected."
548 cp -p "${CWD}/post-install" \
549 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
550 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
553 # Detect declared package names for later replacement
555 if test -n "$replace"
556 then
557 warn \
558 "The following package names has been declared for replacement:" \
559 " $replace"
561 rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
562 for item in $replace
564 echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
565 done
566 unset item
569 # Create meta file for the package information
570 echo "Creating meta file ${full_pkgname}.tlz.txt ..."
571 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
573 # Make a copy of it for the database
574 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
575 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
577 # Produce the package
578 cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
581 # Back to the current working directory
582 cd -- "$CWD" || chkstatus_or_exit
584 # Delete 'srcdir' or 'destdir' if -k was not given
585 if test "$opt_keep" != opt_keep
586 then
587 echo "Deleting temporary directories ..."
589 srcdir="${srcdir%%/*}" # Directory name without parents.
591 if test -r "${TMPDIR}/$srcdir"
592 then
593 if test -z "$keep_srcdir"
594 then
596 cd -- "$TMPDIR" && rm -rf -- "$srcdir" && \
597 echo "removed directory: '${TMPDIR}/${srcdir}'"
598 ); chkstatus_or_exit
599 else
600 warn "The variable 'keep_srcdir' has been set;" \
601 "'${TMPDIR}/${srcdir}' will not be deleted."
605 if test -r "$destdir"
606 then
607 rm -rf -- "$destdir" || chkstatus_or_exit
608 echo "removed directory: '$destdir'"
612 # Install or update the package if -i or -u was passed
613 if test "$opt_nopkg" != opt_nopkg
614 then
615 if test "$opt_install" = opt_install
616 then
617 install_mode "${outdir}/${full_pkgname}.tlz"
618 elif test "$opt_update" = opt_update
619 then
620 upgrade_mode "${outdir}/${full_pkgname}.tlz"
624 echo ""
625 echo "All done for ${CWD}/${recipe}."
626 echo ""
629 create_mode()
631 directory=$(dirname -- "$1")
633 # Perform sanity checks
635 if ! fnmatch '/?*' "$directory"
636 then
637 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
638 exit 4
640 is_readable "$directory" || exit 4
642 name=$(basename -- "$1")
644 if test "$name" = "${name%.tlz}"
645 then
646 warn "Package format '$name' not supported." \
647 "It should be \"name-version-architecture+release.tlz\""
648 exit 4
651 echo "{#} Creating package $name ..."
653 ( umask 022 ; tarlz --solid -9 -cvf - * ) > "${directory}/$name"
654 chkstatus_or_exit 3
656 ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
657 chkstatus_or_exit 4
659 echo "Package created on \`${directory}/${name}'."
660 echo ""
662 # Remove used variables
663 unset directory name
666 delete_mode()
668 expunge="${packagedir}/$(basename -- $1 .tlz)"
670 echo "{<} Deleting package $rootdir${expunge} ..."
672 # Complain if the package directory does not exist
674 test -d "$rootdir${expunge}" || {
675 warn "Package directory '$rootdir${expunge}' does not exist."
676 return 4
679 # Complain if the package directory cannot be well-read
681 is_readable "$rootdir${expunge}" || exit 4
683 # Remove package from Graft control
685 # Scan for possible conflicts, stop if arise
686 if test "$opt_prune" != opt_prune
687 then
688 echo "Checking for possible conflicts ..."
689 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
690 grep ^CONFLICT
691 then
692 warn "" \
693 " A conflict occurred during uninstallation;" \
694 "Unless the -p option is given, this package will be PRESERVED."
695 return 6;
699 # Ignore some signals up to completing the deinstallation
700 trap "" HUP INT QUIT ABRT TERM
702 # Remove objects (files, links or directories) from the target
703 # directory that are in conflict with the package directory
705 echo "Pruning any conflict ..."
706 graft -p -D -u $graft_r -t "$targetdir" "$expunge"
707 chkstatus_or_exit 2
709 echo "Disabling links ..."
710 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
711 chkstatus_or_exit 2
713 # Delete package directory
714 if test "$opt_keep" != opt_keep
715 then
716 echo "Deleting package directory, if it exists as such ..."
718 if test -d "${rootdir}$expunge"
719 then
720 rm -rf -- "${rootdir}$expunge" || chkstatus_or_exit
721 echo "removed directory: '${rootdir}$expunge'"
725 # Reset given signals
726 trap - HUP INT QUIT ABRT TERM
728 # Remove used variables
729 unset expunge
732 install_mode()
734 # Complain if the package cannot be well-read
736 is_readable "$1" || exit 4
738 # Complain if the package does not end in .tlz
740 if ! fnmatch '*.tlz' "$1"
741 then
742 warn "\`${1}' does not end in .tlz"
743 return 4
746 # Make preparations to install the package
748 echo "{>} Installing package $1 ..."
750 # Get the filename
751 name=$(basename -- "$1" .tlz)
753 echo "Checking tarball integrity ..."
754 tarlz -tf "$1" > /dev/null
755 chkstatus_or_exit 3
757 # Create package directory using 'name'
758 if ! test -d "$rootdir${packagedir}/$name"
759 then
760 mkdir -p -- "$rootdir${packagedir}/$name" || chkstatus_or_exit
761 echo "mkdir: created directory '$rootdir${packagedir}/$name'"
764 # Scan for possible conflicts, stop if arise
765 if test "$opt_prune" != opt_prune
766 then
767 echo "Checking for possible conflicts ..."
768 if graft -i -n $graft_r -t "$targetdir" "${packagedir}/$name" 2>&1 | \
769 grep ^CONFLICT
770 then
771 warn "" \
772 " A conflict occurred during installation;" \
773 "Unless the -p option is given, this package won't be LINKED."
774 return 6;
778 # Ignore some signals up to completing the installation
779 trap "" HUP INT QUIT ABRT TERM
781 echo "Decompressing $1 ..."
782 ( cd -- "$rootdir${packagedir}/$name" && tarlz -xf - ) < "$1"
783 chkstatus_or_exit 3
785 # Transite package to Graft control
787 # Remove objects (files, links or directories) from the target
788 # directory that are in conflict with the package directory
789 echo "Pruning any conflict ..."
790 graft -p -D -u $graft_r -t "$targetdir" "${packagedir}/$name"
791 chkstatus_or_exit 2
793 echo "Enabling symbolic links ..."
794 graft -i -P $graft_v $graft_r -t "$targetdir" "${packagedir}/$name"
795 chkstatus_or_exit 2
797 # Avoid unnecessary runs coming from the upgrade_mode(),
798 # this is when the incoming package is **pre-installed**
800 if test "$_isUpgrade" != _isUpgrade.on
801 then
802 # Show package description
803 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
804 then
805 grep '^#' "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
806 elif test -r "${1}.txt"
807 then
808 # From external meta file (current directory)
809 grep '^#' "${1}.txt"
810 else
811 warn "Description file not found for '$name'."
814 # Check and run the post-install script if exist
815 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
816 then
817 echo "Running post-install script for \`${name}' ..."
819 # Rely on 'targetdir' if 'rootdir' is empty
820 cd -- "${rootdir:-$targetdir}"/ && \
821 . "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
825 # Check if there are declared packages for replacement
826 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
827 then
828 while read -r line
830 for replace in "$rootdir${packagedir}/$(pkgbase $line)"-*
832 if ! test -e "$replace"
833 then
834 warn "${replace}: Declared package does not exist. (ignored)"
835 continue;
838 replace="${replace##*/}"
840 # The search for the package to be replaced cannot
841 # be the same to the incoming package, even to the
842 # temporary location coming from the upgrade_mode()
843 if test "$replace" = "$name" || test "$replace" = "${PRVLOC##*/}"
844 then
845 continue;
848 warn "WARNING: Replacing package \`${replace}' ..."
850 # Since the links belongs to the new package, only
851 # those which are not in conflict can be deleted.
852 # To complete, we will remove the package directory
854 graft -d -D -u $graft_r \
855 -t "$targetdir" "$replace" > /dev/null 2>&1
857 rm -rf -- "$rootdir${packagedir}/$replace"
858 done
859 done < "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
860 unset line
864 # Reset given signals
865 trap - HUP INT QUIT ABRT TERM
867 # Remove used variables
868 unset name
871 resolve_mode() {
872 # Complain if the file cannot be well-read
874 is_readable "$1" || exit 4
876 # Complain if the file does not end in .order
878 if ! fnmatch '*.order' "$1"
879 then
880 warn "\`${1}' does not end in .order"
881 return 4
884 # Get a clean list of the file while printing its contents in reverse
885 # order. The last `awk 'in the pipeline eliminates the non-consecutive
886 # lines, the duplicates. Blank lines, colons and parentheses are
887 # simply ignored, comment lines beginning with '#' are allowed
889 awk \
890 '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
891 "$1" | awk '!s[$0]++'
894 upgrade_mode()
896 # Complain if the package does not end in .tlz
898 if ! fnmatch '*.tlz' "$1"
899 then
900 warn "\`${1}' does not end in .tlz"
901 return 4
904 # Get the filename
905 incoming=$(basename -- "$1" .tlz)
907 echo "{^} Upgrading to $incoming ..."
909 # Check packages in the blacklist for installation
911 echo "Checking blacklist ..."
912 for item in $blacklist
914 case $item in
915 ${incoming}*)
916 if test ! -e "$rootdir${packagedir}/$incoming"
917 then
918 warn \
919 "" \
920 " The package declared in the blacklist will be" \
921 "installed instead of being updated ..." \
923 opt_prune=opt_prune install_mode "$1"
924 return 0
927 if is_newer "$1" "$rootdir${packagedir}/$incoming"
928 then
929 warn \
930 "" \
931 "Incoming package is more RECENT than the installed package:" \
932 "" \
933 "$( stat -c "%y %n" "$1" )" \
934 "$( stat -c "%y %n" "$rootdir${packagedir}/$incoming" )" \
935 "" \
936 " The package declared in the blacklist will be" \
937 " installed as part of the upgrade process ..." \
939 opt_prune=opt_prune install_mode "$1"
940 touch "$rootdir${packagedir}/$incoming"
941 return 0
942 else
943 warn \
944 "" \
945 "Blacklisted package is already up-to-date:" \
946 "" \
947 "$( stat -c "%y %n" "$rootdir${packagedir}/$incoming" )" \
948 "$( stat -c "%y %n" "$1" )" \
950 return 6
953 esac
954 done
955 unset item
957 # Check package pre-existence
958 if test "$opt_force" != opt_force && \
959 test -e "$rootdir${packagedir}/$incoming"
960 then
961 warn \
962 "" \
963 " The package to be updated already exist;" \
964 "Unless the -f option is given, this package won't be UPGRADED."
965 return 6;
968 # Prepare the package to install it in a temporary location
970 # Set random directory using packagedir as prefix and 'incoming' as suffix
971 PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
973 # Pre-install the package in the custom 'packagedir'
975 save_packagedir="$rootdir${packagedir}"
976 packagedir="$PRVLOC"
978 echo "Pre-installing package in a temporary location ..."
979 opt_prune=opt_prune # Turn on prune operation.
980 _isUpgrade=_isUpgrade.on install_mode "$1" > /dev/null
981 _isUpgrade=_isUpgrade.off
983 # Restore variable before looking for old packages
984 packagedir=$save_packagedir
985 unset save_packagedir
987 echo "Looking for installations under the same name ..."
988 for long_name in "$rootdir${packagedir}/$(pkgbase $incoming)"*
990 found="${long_name##*/}"
992 # The search for the package to be deleted
993 # cannot be the same to the temporary location
994 test "$long_name" = "$PRVLOC" && continue;
996 fnmatch "$(pkgbase $found)*" "$incoming" || continue;
997 echo "${long_name}: Detected."
999 # A package directory is preserved if -k is given
1000 delete_mode "$found" > /dev/null
1001 done
1002 unset long_name found
1004 # Re-install the package removing the temporary location
1006 install_mode "$1"
1007 opt_prune=opt_prune.off # Turn off prune operation.
1009 echo "Deleting temporary location ..."
1010 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1011 echo "removed directory: '$PRVLOC'"
1013 echo ""
1014 echo "Successful upgrade to '${incoming}'."
1016 # Remove remaining variables
1017 unset incoming PRVLOC
1020 warn_mode()
1022 # Complain if the package cannot be well-read
1024 is_readable "$1" || exit 4
1026 # Complain if the package does not end in .tlz
1028 if ! fnmatch '*.tlz' "$1"
1029 then
1030 warn "\`${1}' does not end in .tlz"
1031 return 4
1034 # List content of files excluding directories
1035 while test -f "$1"
1037 tarlz -tvvf "$1" | awk '!/^drwx/'
1038 chkstatus_or_exit 3
1039 shift;
1040 done
1041 return 0
1044 extract_mode()
1046 # Perform sanity checks before package extraction
1048 is_readable "$1" || exit 4
1050 test -f "$1" || {
1051 warn "\`${1}' is not a regular file."
1052 return 4
1055 # Preparations to extract the package
1057 name=$(basename -- "$1" .tlz)
1059 # Set random directory using 'name' as prefix
1060 PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
1062 # Trap to remove 'PRVDIR' on disruptions
1063 trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
1065 # Create 'PRVDIR' removing access for all but user
1066 ( umask 077 ; mkdir -- $PRVDIR )
1067 mkdir -p -m 700 -- $PRVDIR
1069 echo "Extracting package $name ..."
1070 ( umask 000 ; cd -- $PRVDIR && tarlz -xvf - ) < "$1"
1071 if test $? -ne 0
1072 then
1073 # Try to remove (empty) 'PRVDIR' on failure
1074 rmdir -- $PRVDIR
1075 exit 3;
1077 echo "$name has been extracted on $PRVDIR"
1079 # Reset given signals
1080 trap - HUP INT ABRT TERM
1082 # Remove used variables
1083 unset name PRVDIR
1086 #### Extra functions used in the modes
1088 pkgbase()
1090 string=$(basename -- "$1" .tlz)
1092 # Match cases to print the package name.
1094 # We will take into account the four segments removing
1095 # the last two to print the package (long) name
1096 case $string in
1097 *-*-*+*)
1098 echo "${string%-*-*}"
1101 echo "$string"
1103 esac
1105 unset string
1108 unpack()
1110 for file in "$@"
1112 case $file in
1113 *.tar)
1114 tar -tf "$file" > /dev/null && \
1115 tar -xpf "$file"
1116 chkstatus_or_exit 3
1118 *.tar.gz | *.tgz | *.tar.Z )
1119 gzip -cd "$file" | tar -tf - > /dev/null && \
1120 gzip -cd "$file" | tar -xpf -
1121 chkstatus_or_exit 3
1123 *.tar.bz2 | *.tbz2 | *.tbz )
1124 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1125 bzip2 -cd "$file" | tar -xpf -
1126 chkstatus_or_exit 3
1128 *.tar.lz | *.tlz )
1129 lzip -cd "$file" | tar -tf - > /dev/null && \
1130 lzip -cd "$file" | tar -xpf -
1131 chkstatus_or_exit 3
1133 *.tar.xz | *.txz )
1134 xz -cd "$file" | tar -tf - > /dev/null && \
1135 xz -cd "$file" | tar -xpf -
1136 chkstatus_or_exit 3
1138 *.zip | *.ZIP )
1139 unzip -t "$file" > /dev/null && \
1140 unzip "$file" > /dev/null
1141 chkstatus_or_exit 3
1143 *.gz)
1144 gzip -t "$file" && \
1145 gzip -cd "$file" > "$(basename -- $file .gz)"
1146 chkstatus_or_exit 3
1148 *.Z)
1149 gzip -t "$file" && \
1150 gzip -cd "$file" > "$(basename -- $file .Z)"
1151 chkstatus_or_exit 3
1153 *.bz2)
1154 bzip2 -t "$file" && \
1155 bzip2 -cd "$file" > "$(basename -- $file .bz2)"
1156 chkstatus_or_exit 3
1158 *.lz)
1159 lzip -t "$file" && \
1160 lzip -cd "$file" > "$(basename -- $file .lz)"
1161 chkstatus_or_exit 3
1163 *.xz)
1164 xz -t "$file" && \
1165 xz -cd "$file" > "$(basename -- $file .xz)"
1166 chkstatus_or_exit 3
1169 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1170 exit 1
1171 esac
1172 done
1173 unset file
1176 do_meta()
1178 # Extract information from the recipe to create the meta file.
1180 # The package description is pre-formatted in 78 columns,
1181 # the '#' character and a space is added as prefix to conform
1182 # 80 columns in total
1184 cat << EOF
1185 $(echo "$description" | fold -w 78 | awk '$0="# " $0')
1187 QICFLAGS="$QICFLAGS"
1188 QICXXFLAGS="$QICXXFLAGS"
1189 QILDFLAGS="$QILDFLAGS"
1190 program=$program
1191 version=$version
1192 release=$release
1193 blurb="$(echo "$description" | sed -e '/^$/d;2q')"
1194 homepage="$homepage"
1195 license="$license"
1196 fetch="$fetch"
1197 replace="$replace"
1202 #### Default values
1204 PROGRAM="${0##*/}"
1205 packagedir=@PACKAGEDIR@
1206 targetdir=@TARGETDIR@
1207 blacklist="perl graft tarlz plzip musl glibc"
1208 RC=RC
1209 RCFILE=@SYSCONFDIR@/qirc
1210 opt_install=opt_install.off
1211 opt_update=opt_update.off
1212 opt_force=opt_force.off
1213 opt_keep=opt_keep.off
1214 opt_incr_release=opt_incr_release.off
1215 opt_skipqsts=opt_skipqsts.off
1216 opt_nopkg=opt_nopkg.off
1217 opt_prune=opt_prune.off
1218 rootdir=""
1219 jobs=1
1220 mode=""
1221 verbose=0
1222 graft_v=-v
1223 graft_r=""
1224 _isUpgrade=_isUpgrade.off
1225 TMPDIR="${TMPDIR:=/usr/src/qi/build}"
1226 QICFLAGS="${QICFLAGS:=-g0 -Os}"
1227 QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
1228 QILDFLAGS="${QILDFLAGS:=-s}"
1229 worktree=/usr/src/qi
1230 tardir=${worktree}/sources
1231 outdir=/var/cache/qi/packages
1232 netget="wget -c -w1 -t3 --no-check-certificate"
1233 rsync="rsync -v -a -L -z -i --progress"
1234 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1235 infodir=@INFODIR@
1236 mandir=@MANDIR@
1237 docdir=@DOCDIR@
1239 # Store (default) directory locations
1240 QI_TARGETDIR=$targetdir
1241 QI_PACKAGEDIR=$packagedir
1242 QI_WORKTREE=$worktree
1243 QI_TARDIR=$tardir
1244 QI_OUTDIR=$outdir
1246 #### Parse options
1248 while getopts :bcdiouwxLNP:t:fkvO:W:Z:a:j:1nSpr:hV name
1250 case $name in
1252 if test -z "$mode"
1253 then
1254 readconfig
1255 mode=build_mode
1259 mode=create_mode
1262 readconfig
1263 mode=delete_mode
1266 if test -z "$mode"
1267 then
1268 readconfig
1269 mode=install_mode
1271 if test "$mode" = build_mode
1272 then
1273 opt_install=opt_install
1277 mode=resolve_mode
1280 if test -z "$mode"
1281 then
1282 readconfig
1283 mode=upgrade_mode
1285 if test "$mode" = build_mode
1286 then
1287 opt_update=opt_update
1291 mode=warn_mode
1294 mode=extract_mode
1297 printf "%s\n" \
1298 "QI_TARGETDIR=$QI_TARGETDIR" \
1299 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1300 "QI_WORKTREE=$QI_WORKTREE" \
1301 "QI_TARDIR=$QI_TARDIR" \
1302 "QI_OUTDIR=$QI_OUTDIR"
1303 exit 0
1306 RC=RC.off
1309 packagedir="$OPTARG"
1312 targetdir="$OPTARG"
1315 opt_force=opt_force
1318 opt_keep=opt_keep
1321 verbose=$(( verbose + 1 ))
1324 outdir="$OPTARG"
1327 worktree="$OPTARG"
1330 tardir="$OPTARG"
1333 arch="$OPTARG"
1336 jobs="$OPTARG"
1339 opt_incr_release=opt_incr_release
1342 opt_nopkg=opt_nopkg
1345 opt_skipqsts=opt_skipqsts
1348 opt_prune=opt_prune
1351 rootdir="$OPTARG"
1354 usage
1355 exit 0
1358 version
1359 exit 0
1362 warn "Option '-${OPTARG}' requires an argument"
1363 usage
1364 exit 1
1367 warn "Illegal option -- '-${OPTARG}'"
1368 usage
1369 exit 1
1371 esac
1372 done
1373 shift $(( OPTIND - 1 ))
1375 if test $# -eq 0
1376 then
1377 usage
1378 exit 1
1381 # Program sanity check
1382 for need in awk basename chmod cp dirname find fold graft grep \
1383 mkdir mktemp rm rmdir sed sha256sum stat tarlz
1385 if ! type $need 1> /dev/null 2> /dev/null
1386 then
1387 warn "${PROGRAM}: Prerequisite \`${need}' not found in PATH"
1388 exit 2
1390 done
1391 unset need
1393 # Determine verbosity level/flag
1395 if test "$verbose" -gt 1
1396 then
1397 graft_v=-V
1400 # Read standard input if FILE is -, or when FILE
1401 # is not connected to a terminal.
1403 if test "$1" = - || test ! -t 0
1404 then
1405 # Unset positional parameters setting $# to zero
1406 set --
1408 # Assign remaining arguments to the positional parameters
1409 while read -r input
1411 set -- "$@" "$input"
1412 done
1415 # We need at least one operating mode
1416 if test -z "$mode"
1417 then
1418 usage
1419 exit 4
1422 umask 022; # Remove write permission for group and other.
1424 # Validate 'packagedir' and 'targetdir' as canonical directories
1426 # The single slash '/' does not qualify here
1427 if ! fnmatch '/?*' "$packagedir"
1428 then
1429 warn "${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1430 exit 4
1432 if test ! -d "$packagedir"
1433 then
1434 warn "${PROGRAM}: Package directory \`${packagedir}' does not exist"
1435 exit 4
1438 # The single slash '/' is valid here
1439 if ! fnmatch '/*' "$targetdir"
1440 then
1441 warn "${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1442 exit 4
1444 if test ! -d "$targetdir"
1445 then
1446 warn "${PROGRAM}: Target directory \`${targetdir}' does not exist"
1447 exit 4
1450 # Validate 'rootdir' directory
1452 if test -n "$rootdir"
1453 then
1454 if test -d "$rootdir" && test "$rootdir" != /
1455 then
1456 # Remove slash from the end
1457 rootdir="${rootdir%/}"
1459 # A workaround for graft-2.13+. The specified directory is
1460 # relative to the log file, we prepend it inside the rootdir
1462 eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
1463 mkdir -p -- "$rootdir$(dirname -- $GRAFT_LOGFILE)" || chkstatus_or_exit
1465 # Compose 'rootdir' and log file option to be used with graft(1)
1466 graft_r="-r $rootdir -l $GRAFT_LOGFILE"
1468 # Unset variables coming from eval
1469 unset GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1470 else
1471 warn "${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1472 exit 4
1474 export rootdir
1477 # Ensure 'TMPDIR' creation to prefix temporary files
1479 if test ! -d "$TMPDIR"
1480 then
1481 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1483 readonly TMPDIR
1485 # Process each package or recipe provided on the command-line
1487 for package in "$@"
1489 $mode $package
1490 done