qi: remove tar(1) from the list of prerequisites
[dragora.git] / qi / src / qi.in
blobf74732f8da2f38a6786afd568c455eb69f60a5d9
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 practical and user-friendly package manager." \
33 "" \
34 "Usage: $PROGRAM [OPTION...] [FILE]..." \
35 "" \
36 "Operation mode:" \
37 " -b Build packages using recipes" \
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 " -x Extract packages for debugging purposes" \
44 "" \
45 "Common options:" \
46 " -L Print default directory locations" \
47 " -N Don't read the configuration file" \
48 " -P <DIR> Package directory for (de)installations;" \
49 " (only valid for -i, -d, or -u options)" \
50 " -f Force package upgrade" \
51 " Force build a recipe" \
52 " -t <DIR> Target directory for symbolic links" \
53 " (only valid for -i, -d, or -u options)" \
54 " -k Keep (don't delete) srcdir or destdir" \
55 " Keep (don't delete) package directory" \
56 " (only valid for -b, -d or -u options)" \
57 " -p Prune conflicts on package (de)installation" \
58 " -r Use the named directory as the root directory" \
59 " for installing, deleting, or upgrading packages." \
60 " The target directory, package directory will be" \
61 " relative to this specific directory" \
62 " -v Be verbose (a 2nd -v gives more)" \
63 "" \
64 "Options for 'build' mode (-b):" \
65 " -O <DIR> Where the produced packages are written" \
66 " -W <DIR> Where archives, patches, and recipes are expected" \
67 " -Z <DIR> Where (compressed) sources will be found" \
68 " -a Architecture to use [detected]" \
69 " -j Parallel jobs for the compiler" \
70 " -1 Increment release number (release + 1)" \
71 " -n Don't create a .tlz package" \
72 " -S Selects the option to skip completed recipes" \
73 "" \
74 "Other options:" \
75 " -h Display this help and exit" \
76 " -V Output version information" \
77 "" \
78 "Some influential environment variables:" \
79 " TMPDIR Temporary directory for sources" \
80 " QICFLAGS C compiler flags" \
81 " QICXXFLAGS C++ compiler flags" \
82 " QILDFLAGS Linker flags" \
83 "" \
84 "When FILE is -, read standard input." \
88 version()
90 printf "%s\n" \
91 "$PROGRAM @VERSION@" \
92 "Copyright (C) 2016-2019 Matias Andres Fonzo." \
93 "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
94 "This is free software: you are free to change and redistribute it." \
95 "There is NO WARRANTY, to the extent permitted by law."
98 warn()
100 printf "%s\n" "$@" 1>&2
103 is_readable()
105 if test -e "$1"
106 then
107 if ! test -r "$1"
108 then
109 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
110 return 1
112 else
113 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
114 return 1
118 # Portable alternative to the file operator -nt (among shells)
119 is_newer()
121 if test -n "$(find $1 -prune -newer $2 -print)"
122 then
123 return 0
125 return 1
128 # Determine whether $2 matches pattern $1
129 fnmatch()
131 case $2 in
133 return 0
136 return 1
138 esac
141 chkstatus_or_exit()
143 status=$?
145 if test $status -ne 0
146 then
147 echo "Return status = $status" 1>&2
148 exit ${1-2}; # If not given, defaults to 2
151 unset status
154 readconfig()
156 if test $RC = RC
157 then
158 is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
160 echo "Processing \`${RCFILE}' ..."
162 test -f "$RCFILE" || {
163 warn "${RCFILE} is not a regular file."
164 return 1
167 # Parse config file
168 while IFS='=' read -r variable value
170 case $variable in
171 \#* | "") # Ignore commented or blank lines
172 continue
174 esac
176 # Set variable name avoiding code execution
177 eval "$variable=\${value}"
178 done < "$RCFILE"
182 #### Mode functions
184 build_mode()
186 recipe=$1
188 # A recipe is any valid regular file, the current working directory
189 # has priority over the working tree (or where the recipes reside).
190 # The 'worktree' is the second place where to find a recipe. Also,
191 # we complete the possibility of using the directory name to invoke
192 # a recipe if it contains "recipe" as valid file name.
194 if test ! -f "$recipe"
195 then
196 if test -f "${recipe}/recipe"
197 then
198 recipe="${recipe}/recipe"
199 elif test -f "${worktree}/recipes/${recipe}/recipe"
200 then
201 recipe="${worktree}/recipes/${recipe}/recipe"
205 test -f "$recipe" || {
206 warn "\`${recipe}' is not a regular file."
207 return 4
210 # Complain if the file name is not "recipe"
212 if test "${recipe##*/}" != recipe
213 then
214 warn "\`${recipe}' is not a valid recipe name."
215 return 4
218 # Start preparations to import the recipe
220 # Separate the directory name from the file name,
221 # getting its absolute path and base name
223 CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
224 recipe=$(basename -- "$recipe")
226 # Check readability for load the recipe on success
228 is_readable "${CWD}/$recipe" || exit 4
230 # Find target architecture if 'arch' is not set
231 if test -z "$arch"
232 then
233 arch=$(${CC:-cc} -dumpmachine 2> /dev/null) || arch=unknown
234 arch="${arch%%-*}" # Get the rid of target triplet.
237 # Re-create external directories
238 mkdir -p -- "${worktree}/archive" \
239 "${worktree}/patches" \
240 "${worktree}/recipes" \
241 "$tardir"
243 # Variables treatment for the current and next recipe.
245 # Unset special variables that can only be predefined on
246 # the recipe and not coming from ${sysconfdir}/qirc
248 unset srcdir destdir pkgname pkgversion program version release \
249 fetch description homepage license replace full_pkgname \
250 CFLAGS CXXFLAGS LDFLAGS
252 # The following variables must be saved and restored
253 save_arch="${save_arch:=$arch}"
254 save_jobs="${save_jobs:=$jobs}"
255 save_outdir="${save_outdir:=$outdir}"
256 save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
258 # Reset variable values in case of return
259 arch=$save_arch
260 jobs=$save_jobs
261 outdir=$save_outdir
262 opt_nopkg=$save_opt_nopkg
264 # The following variables cannot be redefined on the recipe
265 readonly worktree netget rsync
267 # Import the recipe
269 echo "{@} Building from ${CWD}/$recipe ..."
270 . "${CWD}/$recipe"
272 # Check for required variables
273 if test -z "$program"
274 then
275 warn "${recipe}: The variable 'program' is not defined."
276 exit 5
278 if test -z "$version"
279 then
280 warn "${recipe}: The variable 'version' 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 # Complete package name adding 'pkgversion-arch+release'
311 full_pkgname="${full_pkgname:=$pkgname-${pkgversion}-${arch}+${release}}"
313 # If a package is going to be created, the existence of a
314 # previous build will be detected and reported. Under normal
315 # conditions the recipe is built as long as it is newer than
316 # the produced package, if not, we warn to the user about it.
317 # Rebuilding the package is possible (through the force ;-)
319 if test "$opt_nopkg" != opt_nopkg && \
320 { test "$opt_force" != opt_force && \
321 test -r "${outdir}/${full_pkgname}.tlz" ; }
322 then
323 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
324 then
325 warn \
326 "" \
327 "The recipe is more RECENT than the detected package:" \
328 "" \
329 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
330 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
331 "" \
332 " This recipe will be processed ..." \
334 elif test -e "${CWD}/post-install" && \
335 is_newer "${CWD}/post-install" "${CWD}/$recipe"
336 then
337 warn \
338 "" \
339 "The post-install script is more RECENT than the recipe:" \
340 "" \
341 "$( stat -c "%y %n" "${CWD}/post-install" )" \
342 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
343 "" \
344 " The recipe will be re-processed ..." \
346 touch "${CWD}/$recipe"
347 elif test "$opt_skipqsts" = opt_skipqsts
348 then
349 warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
350 return 0
351 else
352 warn \
353 "" \
354 "This recipe ALREADY produced a package:" \
355 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
356 "" \
357 "The recipe is still OLDER than the produced package:" \
358 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
359 "" \
360 " Probably nothing has changed." \
363 # In non-interactive mode, the user is asked about
364 # rebuilding the package. In interactive mode,
365 # the user need to pass the option explicitly
366 if test ! -t 0
367 then
368 printf "%b" \
369 "Do you want to rebuild this package?\n" \
370 "1) Yes, built it\n" \
371 "2) No, skipt it [default]\n" \
372 "3) Resume, skipping completed recipes\n" \
373 "Choose an option number: " > /dev/tty
374 IFS= read -r ANSWER < /dev/tty || exit 2;
375 case $ANSWER in
377 echo "$ANSWER" > /dev/tty
378 unset ANSWER
381 unset ANSWER
382 echo "Building UNPROCESSED/MODIFIED recipes ..." > /dev/tty
383 echo ""
384 opt_skipqsts=opt_skipqsts
385 readonly opt_skipqsts
386 return 0
389 unset ANSWER
390 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
391 echo ""
392 return 0
394 esac
395 else
396 warn "Use the -f option to re-process ${CWD}/$recipe." ""
397 return 6;
402 # Fetch remote sources
404 echo "Fetching remote sources if needed ..."
405 if test -n "$fetch"
406 then
407 for origin in $fetch
409 _source="${origin##*/}"
411 echo "Looking for \"$_source\" ..."
413 echo "Verifying checksum file \"${_source}.sha256\" from '${tardir}'"
414 if test -e "${tardir}/${_source}.sha256"
415 then
416 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
417 chkstatus_or_exit
418 continue;
419 else
420 warn "${_source}.sha256: Checksum file does not exist, yet."
423 # Download source or resume if allowed
425 if test ! -e "${tardir}/$_source"
426 then
427 warn " Can't find $_source in ${tardir};" \
428 "attempting to get it from ${origin%/*} ..."
431 case $origin in
432 rsync://*)
434 cd -- "$tardir" && $rsync $origin || exit $?
435 sha256sum $_source > ${_source}.sha256
436 ); chkstatus_or_exit 10
438 *://*)
440 cd -- "$tardir" && $netget $origin || exit $?
441 sha256sum $_source > ${_source}.sha256
442 ); chkstatus_or_exit 10
445 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
446 exit 4
447 esac
448 done
449 unset origin _source
450 else
451 warn "The variable 'fetch' is empty."
454 # Prepare special directories for build the source,
455 # the destination and the output of the package
457 echo "Preparing directories ..."
459 if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
460 then
461 rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
462 echo "removed directory: '${TMPDIR}/$srcdir'"
465 if test -d "$destdir"
466 then
467 rm -rf -- "$destdir" || chkstatus_or_exit
468 echo "removed directory: '$destdir'"
470 mkdir -p -- "$destdir" || chkstatus_or_exit
471 echo "mkdir: created directory '$destdir'"
473 if test ! -d "$outdir"
474 then
475 mkdir -p -- "$outdir" || chkstatus_or_exit
476 echo "mkdir: created directory '$outdir'"
479 echo "Entering to 'TMPDIR': $TMPDIR ..."
480 cd -- "$TMPDIR" || chkstatus_or_exit
482 # Set trap before to run the build() function in order
483 # to catch the return status, exit code 2 if fails
485 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
487 # Determine if the debugging indicators of the shell should be
488 # retained, assuming that it has been previously passed
489 case $- in *x*)
490 _xtrace_flag_is_set=xtrace_flag_is_set ;;
491 esac
493 echo "Running build() ..."
494 build
495 unset build
497 # Turn off possible shell flags coming from the recipe
499 set +e
500 if test "$_xtrace_flag_is_set" != xtrace_flag_is_set
501 then
502 set +x
505 # Reset given signals
506 trap - EXIT HUP INT QUIT ABRT TERM
508 # If 'destdir' is empty, the package won't be created
509 if rmdir -- "$destdir" 2> /dev/null
510 then
511 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
512 opt_nopkg=opt_nopkg
515 # Create (make) the package
517 if test "$opt_nopkg" != opt_nopkg
518 then
519 # Edit the recipe when 'release' is incremented
520 if test "$opt_incr_release" = opt_incr_release
521 then
522 echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
523 ed "${CWD}/$recipe" || chkstatus_or_exit
526 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
528 # Include a recipe copy into the package
529 cp -p "${CWD}/$recipe" \
530 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
531 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
533 # Detect post-install script for inclusion
535 if test -f "${CWD}/post-install"
536 then
537 echo "${CWD}/post-install: Detected."
538 cp -p "${CWD}/post-install" \
539 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
540 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
543 # Detect declared package names for later replacement
545 if test -n "$replace"
546 then
547 warn \
548 "The following package names has been declared for replacement:" \
549 " $replace"
551 rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
552 for item in $replace
554 echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
555 done
556 unset item
559 # Create meta file for the package information
560 echo "Creating meta file ${full_pkgname}.tlz.txt ..."
561 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
563 # Make a copy of it for the database
564 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
565 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
567 # Produce the package
568 cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
571 # Back to the current working directory
572 cd -- "$CWD" || chkstatus_or_exit
574 # Delete 'srcdir' or 'destdir' if -k was not given
575 if test "$opt_keep" != opt_keep
576 then
577 echo "Deleting temporary directories ..."
579 srcdir="${srcdir%%/*}" # Directory name without parents.
581 if test -r "${TMPDIR}/$srcdir"
582 then
583 if test -z "$keep_srcdir"
584 then
586 cd -- "$TMPDIR" && rm -rf -- "$srcdir" && \
587 echo "removed directory: '${TMPDIR}/${srcdir}'"
588 ); chkstatus_or_exit
589 else
590 warn "The variable 'keep_srcdir' has been set;" \
591 "'${TMPDIR}/${srcdir}' will not be deleted."
595 if test -r "$destdir"
596 then
597 rm -rf -- "$destdir" || chkstatus_or_exit
598 echo "removed directory: '$destdir'"
602 # Install or update the package if -i or -u was passed
603 if test "$opt_nopkg" != opt_nopkg
604 then
605 if test "$opt_install" = opt_install
606 then
607 install_mode "${outdir}/${full_pkgname}.tlz"
608 elif test "$opt_update" = opt_update
609 then
610 upgrade_mode "${outdir}/${full_pkgname}.tlz"
614 echo ""
615 echo "All done for ${CWD}/${recipe}."
616 echo ""
619 create_mode()
621 directory=$(dirname -- "$1")
622 name=$(basename -- "$1")
624 # Perform sanity checks
626 is_readable "$directory" || exit 4
628 # Check again to find out if it is a valid directory
629 test -d "$directory" || {
630 warn "Package directory '$name' does not exist."
631 exit 4
634 test "$directory" = . && {
635 warn "Cannot create package on current working directory."
636 exit 4
639 test "$name" = "${name%.tlz}" && {
640 warn \
641 "Package format '$name' not supported." \
642 "It should be \`name-version-architecture+release.tlz'"
643 exit 4
646 echo "{#} Creating package $name ..."
648 ( umask 022 ; tarlz --solid -9 -cvf - * ) > "${directory}/$name"
649 chkstatus_or_exit 3
651 ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
652 chkstatus_or_exit 4
654 echo "Package created \`${directory}/${name}'."
655 echo ""
657 # Remove used variables
658 unset directory name
661 delete_mode()
663 expunge="${packagedir}/$(basename -- $1 .tlz)"
665 echo "{<} Deleting package $rootdir${expunge} ..."
667 # Complain if the package directory does not exist
669 test -d "$rootdir${expunge}" || {
670 warn "Package directory '$rootdir${expunge}' does not exist."
671 return 4
674 # Complain if the package directory cannot be well-read
676 is_readable "$rootdir${expunge}" || exit 4
678 # Remove package from Graft control
680 # Scan for possible conflicts, stop if arise
681 if test "$opt_prune" != opt_prune
682 then
683 echo "Checking for possible conflicts ..."
684 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
685 grep ^CONFLICT
686 then
687 warn "" \
688 " A conflict occurred during uninstallation;" \
689 "Unless the -p option is given, this package will be PRESERVED."
690 return 6;
694 # Ignore some signals up to completing the deinstallation
695 trap "" HUP INT QUIT ABRT TERM
697 # Remove objects (files, links or directories) from the target
698 # directory that are in conflict with the package directory
700 echo "Pruning any conflict ..."
701 graft -p -D -u $graft_r -t "$targetdir" "$expunge"
702 chkstatus_or_exit 2
704 echo "Disabling links ..."
705 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
706 chkstatus_or_exit 2
708 # Delete package directory
709 if test "$opt_keep" != opt_keep
710 then
711 echo "Deleting package directory, if it exists as such ..."
713 if test -d "${rootdir}$expunge"
714 then
715 rm -rf -- "${rootdir}$expunge" || chkstatus_or_exit
716 echo "removed directory: '${rootdir}$expunge'"
720 # Reset given signals
721 trap - HUP INT QUIT ABRT TERM
723 # Remove used variables
724 unset expunge
727 install_mode()
729 # Complain if the package cannot be well-read
731 is_readable "$1" || exit 4
733 # Complain if the package does not end in .tlz
735 if ! fnmatch '*.tlz' "$1"
736 then
737 warn "\`${1}' does not end in .tlz"
738 return 4
741 # Make preparations to install the package
743 echo "{>} Installing package $1 ..."
745 # Get the filename
746 name=$(basename -- "$1" .tlz)
748 echo "Checking tarball integrity ..."
749 tarlz -tf "$1" > /dev/null
750 chkstatus_or_exit 3
752 # Create package directory using 'name'
753 if ! test -d "$rootdir${packagedir}/$name"
754 then
755 mkdir -p -- "$rootdir${packagedir}/$name" || chkstatus_or_exit
756 echo "mkdir: created directory '$rootdir${packagedir}/$name'"
759 # Scan for possible conflicts, stop if arise
760 if test "$opt_prune" != opt_prune
761 then
762 echo "Checking for possible conflicts ..."
763 if graft -i -n $graft_r -t "$targetdir" "${packagedir}/$name" 2>&1 | \
764 grep ^CONFLICT
765 then
766 warn "" \
767 " A conflict occurred during installation;" \
768 "Unless the -p option is given, this package won't be LINKED."
769 return 6;
773 # Ignore some signals up to completing the installation
774 trap "" HUP INT QUIT ABRT TERM
776 echo "Decompressing $1 ..."
777 ( cd -- "$rootdir${packagedir}/$name" && tarlz -xf - ) < "$1"
778 chkstatus_or_exit 3
780 # Transite package to Graft control
782 # Remove objects (files, links or directories) from the target
783 # directory that are in conflict with the package directory
784 echo "Pruning any conflict ..."
785 graft -p -D -u $graft_r -t "$targetdir" "${packagedir}/$name"
786 chkstatus_or_exit 2
788 echo "Enabling symbolic links ..."
789 graft -i -P $graft_v $graft_r -t "$targetdir" "${packagedir}/$name"
790 chkstatus_or_exit 2
792 # Avoid unnecessary runs coming from the upgrade_mode(),
793 # this is when the incoming package is **pre-installed**
795 if test "$_isUpgrade" != _isUpgrade.on
796 then
797 # Show package description
798 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
799 then
800 grep '^#' "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
801 elif test -r "${1}.txt"
802 then
803 # From external meta file (current directory)
804 grep '^#' "${1}.txt"
805 else
806 warn "Description file not found for '$name'."
809 # Check and run the post-install script if exist
810 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
811 then
812 echo "Running post-install script for \`${name}' ..."
814 # Rely on 'targetdir' if 'rootdir' is empty
815 cd -- "${rootdir:-$targetdir}"/ && \
816 . "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
820 # Check if there are declared packages for replacement
821 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
822 then
823 while read -r line
825 for replace in "$rootdir${packagedir}/$(pkgbase $line)"-*
827 if ! test -e "$replace"
828 then
829 warn "${replace}: Declared package does not exist. (ignored)"
830 continue;
833 replace="${replace##*/}"
835 # The search for the package to be replaced cannot
836 # be the same to the incoming package, even to the
837 # temporary location coming from the upgrade_mode()
838 if test "$replace" = "$name" || test "$replace" = "${PRVLOC##*/}"
839 then
840 continue;
843 warn "WARNING: Replacing package \`${replace}' ..."
845 # Since the links belongs to the new package, only
846 # those which are not in conflict can be deleted.
847 # To complete, we will remove the package directory
849 graft -d -D -u $graft_r \
850 -t "$targetdir" "$replace" > /dev/null 2>&1
852 rm -rf -- "$rootdir${packagedir}/$replace"
853 done
854 done < "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
855 unset line
859 # Reset given signals
860 trap - HUP INT QUIT ABRT TERM
862 # Remove used variables
863 unset name
866 resolve_mode() {
867 # Complain if the file cannot be well-read
869 is_readable "$1" || exit 4
871 # Complain if the file does not end in .order
873 if ! fnmatch '*.order' "$1"
874 then
875 warn "\`${1}' does not end in .order"
876 return 4
879 # Get a clean list of the file while prints its content in reverse order,
880 # lines containing: colons, comments, parentheses, end of line, and blank
881 # lines, are removed. The parentheses are used to insert a reference.
882 # The last `awk' in the pipe: removes nonconsecutive lines, duplicate.
883 awk \
884 '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
885 "$1" | awk '!s[$0]++'
888 upgrade_mode()
890 # Complain if the package is not a regular file
892 test -f "$1" || {
893 warn "\`${1}' is not a regular file."
894 return 4
897 # Get the filename
898 incoming=$(basename -- "$1" .tlz)
900 echo "{^} Upgrading to $incoming ..."
902 if test "$opt_force" != opt_force && test -e "${packagedir}/$incoming"
903 then
904 warn "" \
905 " The package to be updated already exist;" \
906 "Unless the -f option is given, this package won't be UPGRADED."
907 return 6;
910 # Check for packages declared in the black list only for installation
912 for item in $blacklist
914 case $item in
915 ${incoming}*)
916 warn \
917 "*" \
918 "* The incoming package will be installed instead of being updated." \
919 "* This may be crucial for the correct functioning of \`${PROGRAM}'." \
921 opt_prune=opt_prune install_mode "$1"
922 return 0;;
923 esac
924 done
925 unset item
927 # Prepare the package to install it in a temporary location
929 # Set random directory using packagedir as prefix and 'incoming' as suffix
930 PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
932 # Pre-install the package in the custom 'packagedir'
934 save_packagedir="$packagedir"
935 packagedir="$PRVLOC"
937 echo "Pre-installing package using temporary location ..."
938 opt_prune=opt_prune # Turn on prune operation.
939 _isUpgrade=_isUpgrade.on install_mode "$1" > /dev/null
940 _isUpgrade=_isUpgrade.off
942 # Restore variable before looking for old packages
943 packagedir=$save_packagedir
944 unset save_packagedir
946 echo "Looking for installations under the same name ..."
947 for long_name in "$rootdir${packagedir}/$(pkgbase $incoming)"*
949 found="${long_name##*/}"
951 # The search for the package to be deleted
952 # cannot be the same to the temporary location
953 test "$long_name" = "$PRVLOC" && continue;
955 fnmatch "$(pkgbase $found)*" "$incoming" || continue;
956 echo "${long_name}: Detected."
958 # A package directory is preserved if -k is given
959 delete_mode "$found" > /dev/null
960 done
961 unset long_name found
963 # Re-install the package removing the temporary location
965 install_mode "$1"
966 opt_prune=opt_prune.off # Turn off prune operation.
968 echo "Deleting temporary location ..."
969 rm -rf -- "$PRVLOC" || chkstatus_or_exit
970 echo "removed directory: '$PRVLOC'"
972 echo ""
973 echo "Successful upgrade to '${incoming}'."
975 # Remove remaining variables
976 unset incoming PRVLOC
979 extract_mode()
981 # Perform sanity checks before package extraction
983 is_readable "$1" || exit 4
985 test -f "$1" || {
986 warn "\`${1}' is not a regular file."
987 return 4
990 # Preparations to extract the package
992 name=$(basename -- "$1" .tlz)
994 # Set random directory using 'name' as prefix
995 PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
997 # Trap to remove 'PRVDIR' on disruptions
998 trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
1000 # Create 'PRVDIR' removing access for all but user
1001 ( umask 077 ; mkdir -- $PRVDIR )
1002 mkdir -p -m 700 -- $PRVDIR
1004 echo "Extracting package $name ..."
1005 ( umask 000 ; cd -- $PRVDIR && tarlz -xvf - ) < "$1"
1006 if test $? -ne 0
1007 then
1008 # Try to remove (empty) 'PRVDIR' on failure
1009 rmdir -- $PRVDIR
1010 exit 3;
1012 echo "$name has been extracted on $PRVDIR"
1014 # Reset given signals
1015 trap - HUP INT ABRT TERM
1017 # Remove used variables
1018 unset name PRVDIR
1021 #### Extra functions used in the modes
1023 pkgbase()
1025 string=$(basename -- "$1" .tlz)
1027 # Match cases to print the package name.
1029 # We will take into account the four segments removing
1030 # the last two to print the package (long) name
1031 case $string in
1032 *-*-*+*)
1033 echo "${string%-*-*}"
1036 echo "$string"
1038 esac
1040 unset string
1043 unpack()
1045 for file in "$@"
1047 case $file in
1048 *.tar)
1049 tar -tf "$file" > /dev/null && \
1050 tar -xpf "$file"
1051 chkstatus_or_exit 3
1053 *.tar.gz | *.tgz | *.tar.Z )
1054 gzip -cd "$file" | tar -tf - > /dev/null && \
1055 gzip -cd "$file" | tar -xpf -
1056 chkstatus_or_exit 3
1058 *.tar.bz2 | *.tbz2 | *.tbz )
1059 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1060 bzip2 -cd "$file" | tar -xpf -
1061 chkstatus_or_exit 3
1063 *.tar.lz | *.tlz )
1064 lzip -cd "$file" | tar -tf - > /dev/null && \
1065 lzip -cd "$file" | tar -xpf -
1066 chkstatus_or_exit 3
1068 *.tar.xz | *.txz )
1069 xz -cd "$file" | tar -tf - > /dev/null && \
1070 xz -cd "$file" | tar -xpf -
1071 chkstatus_or_exit 3
1073 *.zip | *.ZIP )
1074 unzip -t "$file" > /dev/null && \
1075 unzip "$file" > /dev/null
1076 chkstatus_or_exit 3
1078 *.gz)
1079 gzip -t "$file" && \
1080 gzip -cd "$file" > "$(basename -- $file .gz)"
1081 chkstatus_or_exit 3
1083 *.Z)
1084 gzip -t "$file" && \
1085 gzip -cd "$file" > "$(basename -- $file .Z)"
1086 chkstatus_or_exit 3
1088 *.bz2)
1089 bzip2 -t "$file" && \
1090 bzip2 -cd "$file" > "$(basename -- $file .bz2)"
1091 chkstatus_or_exit 3
1093 *.lz)
1094 lzip -t "$file" && \
1095 lzip -cd "$file" > "$(basename -- $file .lz)"
1096 chkstatus_or_exit 3
1098 *.xz)
1099 xz -t "$file" && \
1100 xz -cd "$file" > "$(basename -- $file .xz)"
1101 chkstatus_or_exit 3
1104 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1105 exit 1
1106 esac
1107 done
1108 unset file
1111 do_meta()
1113 # Extract information from the recipe to create the meta file.
1115 # The package description is pre-formatted in 78 columns,
1116 # the '#' character and a space is added as prefix to conform
1117 # 80 columns in total
1119 cat << EOF
1120 $(echo "$description" | fold -w 78 | awk '$0="# " $0')
1122 QICFLAGS="$QICFLAGS"
1123 QICXXFLAGS="$QICXXFLAGS"
1124 QILDFLAGS="$QILDFLAGS"
1125 program=$program
1126 version=$version
1127 release=$release
1128 blurb="$(echo "$description" | sed -e '/^$/d;2q')"
1129 homepage="$homepage"
1130 license="$license"
1131 fetch="$fetch"
1132 replace="$replace"
1137 #### Default values
1139 PROGRAM="${0##*/}"
1140 packagedir=@PACKAGEDIR@
1141 targetdir=@TARGETDIR@
1142 blacklist="perl graft tarlz plzip musl glibc"
1143 RC=RC
1144 RCFILE=@SYSCONFDIR@/qirc
1145 opt_install=opt_install.off
1146 opt_update=opt_update.off
1147 opt_force=opt_force.off
1148 opt_keep=opt_keep.off
1149 opt_incr_release=opt_incr_release.off
1150 opt_skipqsts=opt_skipqsts.off
1151 opt_nopkg=opt_nopkg.off
1152 opt_prune=opt_prune.off
1153 rootdir=""
1154 jobs=1
1155 mode=""
1156 verbose=0
1157 graft_v=-v
1158 graft_r=""
1159 _isUpgrade=_isUpgrade.off
1160 TMPDIR="${TMPDIR:=/usr/src/qi/build}"
1161 QICFLAGS="${QICFLAGS:=-g0 -Os}"
1162 QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
1163 QILDFLAGS="${QILDFLAGS:=-s}"
1164 worktree=/usr/src/qi
1165 tardir=${worktree}/sources
1166 outdir=/var/cache/qi/packages
1167 netget="wget -c -w1 -t3 --no-check-certificate"
1168 rsync="rsync -v -a -L -z -i --progress"
1169 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1170 infodir=@INFODIR@
1171 mandir=@MANDIR@
1172 docdir=@DOCDIR@
1174 # Store (default) directory locations
1175 QI_TARGETDIR=$targetdir
1176 QI_PACKAGEDIR=$packagedir
1177 QI_WORKTREE=$worktree
1178 QI_TARDIR=$tardir
1179 QI_OUTDIR=$outdir
1181 #### Parse options
1183 while getopts :bcdiouxLNP:t:fkvO:W:Z:a:j:1nSpr:hV name
1185 case $name in
1187 if test -z "$mode"
1188 then
1189 readconfig
1190 mode=build_mode
1194 mode=create_mode
1197 readconfig
1198 mode=delete_mode
1201 if test -z "$mode"
1202 then
1203 readconfig
1204 mode=install_mode
1206 if test "$mode" = build_mode
1207 then
1208 opt_install=opt_install
1212 mode=resolve_mode
1215 if test -z "$mode"
1216 then
1217 readconfig
1218 mode=upgrade_mode
1220 if test "$mode" = build_mode
1221 then
1222 opt_update=opt_update
1226 mode=extract_mode
1229 printf "%s\n" \
1230 "QI_TARGETDIR=$QI_TARGETDIR" \
1231 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1232 "QI_WORKTREE=$QI_WORKTREE" \
1233 "QI_TARDIR=$QI_TARDIR" \
1234 "QI_OUTDIR=$QI_OUTDIR"
1235 exit 0
1238 RC=RC.off
1241 packagedir="$OPTARG"
1244 targetdir="$OPTARG"
1247 opt_force=opt_force
1250 opt_keep=opt_keep
1253 verbose=$(( verbose + 1 ))
1256 outdir="$OPTARG"
1259 worktree="$OPTARG"
1262 tardir="$OPTARG"
1265 arch="$OPTARG"
1268 jobs="$OPTARG"
1271 opt_incr_release=opt_incr_release
1274 opt_nopkg=opt_nopkg
1277 opt_skipqsts=opt_skipqsts
1280 opt_prune=opt_prune
1283 rootdir="$OPTARG"
1286 usage
1287 exit 0
1290 version
1291 exit 0
1294 warn "Option '-${OPTARG}' requires an argument"
1295 usage
1296 exit 1
1299 warn "Illegal option -- '-${OPTARG}'"
1300 usage
1301 exit 1
1303 esac
1304 done
1305 shift $(( OPTIND - 1 ))
1307 if test $# -eq 0
1308 then
1309 usage
1310 exit 1
1313 # Program sanity check
1314 for need in awk basename chmod cp dirname find fold graft grep \
1315 mkdir mktemp rm rmdir sed sha256sum stat tarlz
1317 if ! type $need 1> /dev/null 2> /dev/null
1318 then
1319 warn "${PROGRAM}: cannot operate without ${need}(1): Check your PATH"
1320 exit 2
1322 done
1323 unset need
1325 # Determine verbosity level/flag
1327 if test "$verbose" -gt 1
1328 then
1329 graft_v=-V
1332 # Read standard input if FILE is -, or when FILE
1333 # is not connected to a terminal.
1335 if test "$1" = - || test ! -t 0
1336 then
1337 # Unset positional parameters setting $# to zero
1338 set --
1340 # Assign remaining arguments to the positional parameters
1341 while read -r input
1343 set -- "$@" "$input"
1344 done
1347 # We need at least one operating mode
1348 if test -z "$mode"
1349 then
1350 usage
1351 exit 4
1354 umask 022; # Remove write permission for group and other.
1356 # Validate 'rootdir' directory
1358 if test -n "$rootdir"
1359 then
1360 if test -d "$rootdir" && test "$rootdir" != /
1361 then
1362 # Remove slash from the end
1363 rootdir="${rootdir%/}"
1365 # A workaround for graft-2.13+. The specified directory is
1366 # relative to the log file, we prepend it inside the rootdir
1368 eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
1369 mkdir -p -- "$rootdir$(dirname -- $GRAFT_LOGFILE)" || chkstatus_or_exit
1371 # Compose 'rootdir' and log file option to be used with graft(1)
1372 graft_r="-r $rootdir -l $GRAFT_LOGFILE"
1374 # Unset variables coming from eval
1375 unset GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1376 else
1377 warn "${PROGRAM}: \`${rootdir}' is not a qualified root directory"
1378 exit 4
1380 export rootdir
1383 # Ensure 'TMPDIR' creation to prefix temporary files
1385 if test ! -d "$TMPDIR"
1386 then
1387 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1389 readonly TMPDIR
1391 # Process each package or recipe provided on the command-line
1393 for package in "$@"
1395 $mode $package
1396 done