qi: the creation mode to produce packages has been simplified
[dragora.git] / qi / src / qi.in
blob2dbef334a9186abdfcbba7509cc45b7390fda83e
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 " -w Warn about files that will be installed" \
44 " -x Extract packages for debugging purposes" \
45 "" \
46 "Common options:" \
47 " -L Print default directory locations" \
48 " -N Don't read the configuration file" \
49 " -P <DIR> Package directory for (de)installations;" \
50 " (only valid for -i, -d, or -u options)" \
51 " -f Force package upgrade" \
52 " Force build a recipe" \
53 " -t <DIR> Target directory for symbolic links" \
54 " (only valid for -i, -d, or -u options)" \
55 " -k Keep (don't delete) srcdir or destdir" \
56 " Keep (don't delete) package directory" \
57 " (only valid for -b, -d or -u options)" \
58 " -p Prune conflicts on package (de)installation" \
59 " -r Use the named directory as the root directory" \
60 " for installing, deleting, or upgrading packages." \
61 " The target directory, package directory will be" \
62 " relative to this specific directory" \
63 " -v Be verbose (a 2nd -v gives more)" \
64 "" \
65 "Options for 'build' mode (-b):" \
66 " -O <DIR> Where the produced packages are written" \
67 " -W <DIR> Where archives, patches, and recipes are expected" \
68 " -Z <DIR> Where (compressed) sources will be found" \
69 " -a Architecture to use [detected]" \
70 " -j Parallel jobs for the compiler" \
71 " -1 Increment release number (release + 1)" \
72 " -n Don't create a .tlz package" \
73 " -S Selects the option to skip completed recipes" \
74 "" \
75 "Informative options:" \
76 " -h Display this help and exit" \
77 " -V Output version information" \
78 "" \
79 "Some influential environment variables:" \
80 " TMPDIR Temporary directory for sources" \
81 " QICFLAGS C compiler flags" \
82 " QICXXFLAGS C++ compiler flags" \
83 " QILDFLAGS Linker flags" \
84 "" \
85 "When FILE is -, read standard input." \
89 version()
91 printf "%s\n" \
92 "$PROGRAM @VERSION@" \
93 "Copyright (C) 2016-2019 Matias Andres Fonzo." \
94 "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
95 "This is free software: you are free to change and redistribute it." \
96 "There is NO WARRANTY, to the extent permitted by law."
99 warn()
101 printf "%s\n" "$@" 1>&2
104 is_readable()
106 if test -e "$1"
107 then
108 if ! test -r "$1"
109 then
110 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
111 return 1
113 else
114 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
115 return 1
119 # Portable alternative to the file operator -nt (among shells)
120 is_newer()
122 if test -n "$(find $1 -prune -newer $2 -print)"
123 then
124 return 0
126 return 1
129 # Determine whether $2 matches pattern $1
130 fnmatch()
132 case $2 in
134 return 0
137 return 1
139 esac
142 chkstatus_or_exit()
144 status=$?
146 if test $status -ne 0
147 then
148 echo "Return status = $status" 1>&2
149 exit ${1-2}; # If not given, defaults to 2
152 unset status
155 readconfig()
157 if test $RC = RC
158 then
159 is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
161 echo "Processing \`${RCFILE}' ..."
163 test -f "$RCFILE" || {
164 warn "${RCFILE} is not a regular file."
165 return 1
168 # Parse config file
169 while IFS='=' read -r variable value
171 case $variable in
172 \#* | "") # Ignore commented or blank lines
173 continue
175 esac
177 # Set variable name avoiding code execution
178 eval "$variable=\${value}"
179 done < "$RCFILE"
183 #### Mode functions
185 build_mode()
187 recipe=$1
189 # A recipe is any valid regular file, the current working directory
190 # has priority over the working tree (or where the recipes reside).
191 # The 'worktree' is the second place where to find a recipe. Also,
192 # we complete the possibility of using the directory name to invoke
193 # a recipe if it contains "recipe" as valid file name.
195 if test ! -f "$recipe"
196 then
197 if test -f "${recipe}/recipe"
198 then
199 recipe="${recipe}/recipe"
200 elif test -f "${worktree}/recipes/${recipe}/recipe"
201 then
202 recipe="${worktree}/recipes/${recipe}/recipe"
206 test -f "$recipe" || {
207 warn "\`${recipe}' is not a regular file."
208 return 4
211 # Complain if the file name is not "recipe"
213 if test "${recipe##*/}" != recipe
214 then
215 warn "\`${recipe}' is not a valid recipe name."
216 return 4
219 # Start preparations to import the recipe
221 # Separate the directory name from the file name,
222 # getting its absolute path and base name
224 CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
225 recipe=$(basename -- "$recipe")
227 # Check readability for load the recipe on success
229 is_readable "${CWD}/$recipe" || exit 4
231 # Find target architecture if 'arch' is not set
232 if test -z "$arch"
233 then
234 arch=$(${CC:-cc} -dumpmachine 2> /dev/null) || arch=unknown
235 arch="${arch%%-*}" # Get the rid of target triplet.
238 # Re-create external directories
239 mkdir -p -- "${worktree}/archive" \
240 "${worktree}/patches" \
241 "${worktree}/recipes" \
242 "$tardir"
244 # Variables treatment for the current and next recipe.
246 # Unset special variables that can only be predefined on
247 # the recipe and not coming from ${sysconfdir}/qirc
249 unset srcdir destdir pkgname pkgversion program version release \
250 fetch description homepage license replace full_pkgname \
251 CFLAGS CXXFLAGS LDFLAGS
253 # The following variables must be saved and restored
254 save_arch="${save_arch:=$arch}"
255 save_jobs="${save_jobs:=$jobs}"
256 save_outdir="${save_outdir:=$outdir}"
257 save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
259 # Reset variable values in case of return
260 arch=$save_arch
261 jobs=$save_jobs
262 outdir=$save_outdir
263 opt_nopkg=$save_opt_nopkg
265 # The following variables cannot be redefined on the recipe
266 readonly worktree netget rsync
268 # Import the recipe
270 echo "{@} Building from ${CWD}/$recipe ..."
271 . "${CWD}/$recipe"
273 # Check for required variables
274 if test -z "$program"
275 then
276 warn "${recipe}: The variable 'program' is not defined."
277 exit 5
279 if test -z "$version"
280 then
281 warn "${recipe}: The variable 'version' is not defined."
282 exit 5
284 if test -z "$release"
285 then
286 warn "${recipe}: The variable 'release' is not defined."
287 exit 5
290 # Pre-settings before to start building
292 # Increment the release number if the option was given
293 if test "$opt_incr_release" = opt_incr_release
294 then
295 release=$(( release + 1 ))
298 # Allow the dot as definition for 'tardir'
299 if test "$tardir" = .
300 then
301 tardir="$CWD"
304 # Set default values for the following special variables
306 pkgname="${pkgname:=$program}"
307 pkgversion="${pkgversion:=$version}"
308 srcdir="${srcdir:=${program}-$version}"
309 destdir="${destdir:=${TMPDIR}/package-$pkgname}"
311 # Complete package name adding 'pkgversion-arch+release'
312 full_pkgname="${full_pkgname:=$pkgname-${pkgversion}-${arch}+${release}}"
314 # If a package is going to be created, the existence of a
315 # previous build will be detected and reported. Under normal
316 # conditions the recipe is built as long as it is newer than
317 # the produced package, if not, we warn to the user about it.
318 # Rebuilding the package is possible (through the force ;-)
320 if test "$opt_nopkg" != opt_nopkg && \
321 { test "$opt_force" != opt_force && \
322 test -e "${outdir}/${full_pkgname}.tlz" ; }
323 then
324 if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
325 then
326 warn \
327 "" \
328 "The recipe is more RECENT than the detected package:" \
329 "" \
330 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
331 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
332 "" \
333 " This recipe will be processed ..." \
335 elif test -e "${CWD}/post-install" && \
336 is_newer "${CWD}/post-install" "${CWD}/$recipe"
337 then
338 warn \
339 "" \
340 "The post-install script is more RECENT than the recipe:" \
341 "" \
342 "$( stat -c "%y %n" "${CWD}/post-install" )" \
343 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
344 "" \
345 " The recipe will be re-processed ..." \
347 touch "${CWD}/$recipe"
348 elif test "$opt_skipqsts" = opt_skipqsts
349 then
350 warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
351 return 0
352 else
353 warn \
354 "" \
355 "This recipe ALREADY produced a package:" \
356 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
357 "" \
358 "The recipe is still OLDER than the produced package:" \
359 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
360 "" \
361 " Probably nothing has changed." \
364 # In non-interactive mode, the user is asked about
365 # rebuilding the package. In interactive mode,
366 # the user need to pass the option explicitly
367 if test ! -t 0
368 then
369 printf "%b" \
370 "Do you want to rebuild this package?\n" \
371 "1) Yes, built it\n" \
372 "2) No, skipt it [default]\n" \
373 "3) Resume, skipping completed recipes\n" \
374 "Choose an option number: " > /dev/tty
375 IFS= read -r ANSWER < /dev/tty || exit 2;
376 case $ANSWER in
378 echo "$ANSWER" > /dev/tty
379 unset ANSWER
382 unset ANSWER
383 echo "Building UNPROCESSED/MODIFIED recipes ..." > /dev/tty
384 echo ""
385 opt_skipqsts=opt_skipqsts
386 readonly opt_skipqsts
387 return 0
390 unset ANSWER
391 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
392 echo ""
393 return 0
395 esac
396 else
397 warn "Use the -f option to re-process ${CWD}/$recipe." ""
398 return 6;
403 # Fetch remote sources
405 echo "Fetching remote sources if needed ..."
406 if test -n "$fetch"
407 then
408 for origin in $fetch
410 _source="${origin##*/}"
412 echo "Looking for \"$_source\" ..."
414 echo "Verifying checksum file \"${_source}.sha256\" from '${tardir}'"
415 if test -e "${tardir}/${_source}.sha256"
416 then
417 ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
418 chkstatus_or_exit
419 continue;
420 else
421 warn "${_source}.sha256: Checksum file does not exist, yet."
424 # Download source or resume if allowed
426 if test ! -e "${tardir}/$_source"
427 then
428 warn " Can't find $_source in ${tardir};" \
429 "attempting to get it from ${origin%/*} ..."
432 case $origin in
433 rsync://*)
435 cd -- "$tardir" && $rsync $origin || exit $?
436 sha256sum $_source > ${_source}.sha256
437 ); chkstatus_or_exit 10
439 *://*)
441 cd -- "$tardir" && $netget $origin || exit $?
442 sha256sum $_source > ${_source}.sha256
443 ); chkstatus_or_exit 10
446 warn "${PROGRAM}: Unrecognized protocol for ${origin}."
447 exit 4
448 esac
449 done
450 unset origin _source
451 else
452 warn "The variable 'fetch' is empty."
455 # Prepare special directories for build the source,
456 # the destination and the output of the package
458 echo "Preparing directories ..."
460 if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
461 then
462 rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
463 echo "removed directory: '${TMPDIR}/$srcdir'"
466 if test -d "$destdir"
467 then
468 rm -rf -- "$destdir" || chkstatus_or_exit
469 echo "removed directory: '$destdir'"
471 mkdir -p -- "$destdir" || chkstatus_or_exit
472 echo "mkdir: created directory '$destdir'"
474 if test ! -d "$outdir"
475 then
476 mkdir -p -- "$outdir" || chkstatus_or_exit
477 echo "mkdir: created directory '$outdir'"
480 echo "Entering to 'TMPDIR': $TMPDIR ..."
481 cd -- "$TMPDIR" || chkstatus_or_exit
483 # Set trap before to run the build() function in order
484 # to catch the return status, exit code 2 if fails
486 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
488 # Determine if the debugging indicators of the shell should be
489 # retained, assuming that it has been previously passed
490 case $- in *x*)
491 _xtrace_flag_is_set=xtrace_flag_is_set ;;
492 esac
494 echo "Running build() ..."
495 build
496 unset build
498 # Turn off possible shell flags coming from the recipe
500 set +e
501 if test "$_xtrace_flag_is_set" != xtrace_flag_is_set
502 then
503 set +x
506 # Reset given signals
507 trap - EXIT HUP INT QUIT ABRT TERM
509 # If 'destdir' is empty, the package won't be created
510 if rmdir -- "$destdir" 2> /dev/null
511 then
512 warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
513 opt_nopkg=opt_nopkg
516 # Create (make) the package
518 if test "$opt_nopkg" != opt_nopkg
519 then
520 # Edit the recipe when 'release' is incremented
521 if test "$opt_incr_release" = opt_incr_release
522 then
523 echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
524 ed "${CWD}/$recipe" || chkstatus_or_exit
527 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
529 # Include a recipe copy into the package
530 cp -p "${CWD}/$recipe" \
531 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
532 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
534 # Detect post-install script for inclusion
536 if test -f "${CWD}/post-install"
537 then
538 echo "${CWD}/post-install: Detected."
539 cp -p "${CWD}/post-install" \
540 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
541 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
544 # Detect declared package names for later replacement
546 if test -n "$replace"
547 then
548 warn \
549 "The following package names has been declared for replacement:" \
550 " $replace"
552 rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
553 for item in $replace
555 echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
556 done
557 unset item
560 # Create meta file for the package information
561 echo "Creating meta file ${full_pkgname}.tlz.txt ..."
562 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
564 # Make a copy of it for the database
565 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
566 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
568 # Produce the package
569 cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
572 # Back to the current working directory
573 cd -- "$CWD" || chkstatus_or_exit
575 # Delete 'srcdir' or 'destdir' if -k was not given
576 if test "$opt_keep" != opt_keep
577 then
578 echo "Deleting temporary directories ..."
580 srcdir="${srcdir%%/*}" # Directory name without parents.
582 if test -r "${TMPDIR}/$srcdir"
583 then
584 if test -z "$keep_srcdir"
585 then
587 cd -- "$TMPDIR" && rm -rf -- "$srcdir" && \
588 echo "removed directory: '${TMPDIR}/${srcdir}'"
589 ); chkstatus_or_exit
590 else
591 warn "The variable 'keep_srcdir' has been set;" \
592 "'${TMPDIR}/${srcdir}' will not be deleted."
596 if test -r "$destdir"
597 then
598 rm -rf -- "$destdir" || chkstatus_or_exit
599 echo "removed directory: '$destdir'"
603 # Install or update the package if -i or -u was passed
604 if test "$opt_nopkg" != opt_nopkg
605 then
606 if test "$opt_install" = opt_install
607 then
608 install_mode "${outdir}/${full_pkgname}.tlz"
609 elif test "$opt_update" = opt_update
610 then
611 upgrade_mode "${outdir}/${full_pkgname}.tlz"
615 echo ""
616 echo "All done for ${CWD}/${recipe}."
617 echo ""
620 create_mode()
622 directory=$(dirname -- "$1")
624 # Perform sanity checks
626 if ! fnmatch '/?*' "$directory"
627 then
628 warn "${PROGRAM}: Output directory \`${directory}' is not fully qualified"
629 exit 4
631 is_readable "$directory" || exit 4
633 name=$(basename -- "$1")
635 if test "$name" = "${name%.tlz}"
636 then
637 warn "Package format '$name' not supported." \
638 "It should be \"name-version-architecture+release.tlz\""
639 exit 4
642 echo "{#} Creating package $name ..."
644 ( umask 022 ; tarlz --solid -9 -cvf - * ) > "${directory}/$name"
645 chkstatus_or_exit 3
647 ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
648 chkstatus_or_exit 4
650 echo "Package created on \`${directory}/${name}'."
651 echo ""
653 # Remove used variables
654 unset directory name
657 delete_mode()
659 expunge="${packagedir}/$(basename -- $1 .tlz)"
661 echo "{<} Deleting package $rootdir${expunge} ..."
663 # Complain if the package directory does not exist
665 test -d "$rootdir${expunge}" || {
666 warn "Package directory '$rootdir${expunge}' does not exist."
667 return 4
670 # Complain if the package directory cannot be well-read
672 is_readable "$rootdir${expunge}" || exit 4
674 # Remove package from Graft control
676 # Scan for possible conflicts, stop if arise
677 if test "$opt_prune" != opt_prune
678 then
679 echo "Checking for possible conflicts ..."
680 if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
681 grep ^CONFLICT
682 then
683 warn "" \
684 " A conflict occurred during uninstallation;" \
685 "Unless the -p option is given, this package will be PRESERVED."
686 return 6;
690 # Ignore some signals up to completing the deinstallation
691 trap "" HUP INT QUIT ABRT TERM
693 # Remove objects (files, links or directories) from the target
694 # directory that are in conflict with the package directory
696 echo "Pruning any conflict ..."
697 graft -p -D -u $graft_r -t "$targetdir" "$expunge"
698 chkstatus_or_exit 2
700 echo "Disabling links ..."
701 graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
702 chkstatus_or_exit 2
704 # Delete package directory
705 if test "$opt_keep" != opt_keep
706 then
707 echo "Deleting package directory, if it exists as such ..."
709 if test -d "${rootdir}$expunge"
710 then
711 rm -rf -- "${rootdir}$expunge" || chkstatus_or_exit
712 echo "removed directory: '${rootdir}$expunge'"
716 # Reset given signals
717 trap - HUP INT QUIT ABRT TERM
719 # Remove used variables
720 unset expunge
723 install_mode()
725 # Complain if the package cannot be well-read
727 is_readable "$1" || exit 4
729 # Complain if the package does not end in .tlz
731 if ! fnmatch '*.tlz' "$1"
732 then
733 warn "\`${1}' does not end in .tlz"
734 return 4
737 # Make preparations to install the package
739 echo "{>} Installing package $1 ..."
741 # Get the filename
742 name=$(basename -- "$1" .tlz)
744 echo "Checking tarball integrity ..."
745 tarlz -tf "$1" > /dev/null
746 chkstatus_or_exit 3
748 # Create package directory using 'name'
749 if ! test -d "$rootdir${packagedir}/$name"
750 then
751 mkdir -p -- "$rootdir${packagedir}/$name" || chkstatus_or_exit
752 echo "mkdir: created directory '$rootdir${packagedir}/$name'"
755 # Scan for possible conflicts, stop if arise
756 if test "$opt_prune" != opt_prune
757 then
758 echo "Checking for possible conflicts ..."
759 if graft -i -n $graft_r -t "$targetdir" "${packagedir}/$name" 2>&1 | \
760 grep ^CONFLICT
761 then
762 warn "" \
763 " A conflict occurred during installation;" \
764 "Unless the -p option is given, this package won't be LINKED."
765 return 6;
769 # Ignore some signals up to completing the installation
770 trap "" HUP INT QUIT ABRT TERM
772 echo "Decompressing $1 ..."
773 ( cd -- "$rootdir${packagedir}/$name" && tarlz -xf - ) < "$1"
774 chkstatus_or_exit 3
776 # Transite package to Graft control
778 # Remove objects (files, links or directories) from the target
779 # directory that are in conflict with the package directory
780 echo "Pruning any conflict ..."
781 graft -p -D -u $graft_r -t "$targetdir" "${packagedir}/$name"
782 chkstatus_or_exit 2
784 echo "Enabling symbolic links ..."
785 graft -i -P $graft_v $graft_r -t "$targetdir" "${packagedir}/$name"
786 chkstatus_or_exit 2
788 # Avoid unnecessary runs coming from the upgrade_mode(),
789 # this is when the incoming package is **pre-installed**
791 if test "$_isUpgrade" != _isUpgrade.on
792 then
793 # Show package description
794 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
795 then
796 grep '^#' "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
797 elif test -r "${1}.txt"
798 then
799 # From external meta file (current directory)
800 grep '^#' "${1}.txt"
801 else
802 warn "Description file not found for '$name'."
805 # Check and run the post-install script if exist
806 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
807 then
808 echo "Running post-install script for \`${name}' ..."
810 # Rely on 'targetdir' if 'rootdir' is empty
811 cd -- "${rootdir:-$targetdir}"/ && \
812 . "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
816 # Check if there are declared packages for replacement
817 if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
818 then
819 while read -r line
821 for replace in "$rootdir${packagedir}/$(pkgbase $line)"-*
823 if ! test -e "$replace"
824 then
825 warn "${replace}: Declared package does not exist. (ignored)"
826 continue;
829 replace="${replace##*/}"
831 # The search for the package to be replaced cannot
832 # be the same to the incoming package, even to the
833 # temporary location coming from the upgrade_mode()
834 if test "$replace" = "$name" || test "$replace" = "${PRVLOC##*/}"
835 then
836 continue;
839 warn "WARNING: Replacing package \`${replace}' ..."
841 # Since the links belongs to the new package, only
842 # those which are not in conflict can be deleted.
843 # To complete, we will remove the package directory
845 graft -d -D -u $graft_r \
846 -t "$targetdir" "$replace" > /dev/null 2>&1
848 rm -rf -- "$rootdir${packagedir}/$replace"
849 done
850 done < "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
851 unset line
855 # Reset given signals
856 trap - HUP INT QUIT ABRT TERM
858 # Remove used variables
859 unset name
862 resolve_mode() {
863 # Complain if the file cannot be well-read
865 is_readable "$1" || exit 4
867 # Complain if the file does not end in .order
869 if ! fnmatch '*.order' "$1"
870 then
871 warn "\`${1}' does not end in .order"
872 return 4
875 # Get a clean list of the file while prints its content in reverse order,
876 # lines containing: colons, comments, parentheses, end of line, and blank
877 # lines, are removed. The parentheses are used to insert a reference.
878 # The last `awk' in the pipe: removes nonconsecutive lines, duplicate.
879 awk \
880 '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
881 "$1" | awk '!s[$0]++'
884 upgrade_mode()
886 # Complain if the package does not end in .tlz
888 if ! fnmatch '*.tlz' "$1"
889 then
890 warn "\`${1}' does not end in .tlz"
891 return 4
894 # Get the filename
895 incoming=$(basename -- "$1" .tlz)
897 echo "{^} Upgrading to $incoming ..."
899 # Check packages in the blacklist for installation
901 echo "Checking blacklist ..."
902 for item in $blacklist
904 case $item in
905 ${incoming}*)
906 if test ! -e "$rootdir${packagedir}/$incoming"
907 then
908 warn \
909 "" \
910 " The package declared in the blacklist will be" \
911 "installed instead of being updated ..." \
913 opt_prune=opt_prune install_mode "$1"
914 return 0
917 if is_newer "$1" "$rootdir${packagedir}/$incoming"
918 then
919 warn \
920 "" \
921 "Incoming package is more RECENT than the installed package:" \
922 "" \
923 "$( stat -c "%y %n" "$1" )" \
924 "$( stat -c "%y %n" "$rootdir${packagedir}/$incoming" )" \
925 "" \
926 " The package declared in the blacklist will be" \
927 " installed as part of the upgrade process ..." \
929 opt_prune=opt_prune install_mode "$1"
930 touch "$rootdir${packagedir}/$incoming"
931 return 0
932 else
933 warn \
934 "" \
935 "Blacklisted package is already up-to-date:" \
936 "" \
937 "$( stat -c "%y %n" "$rootdir${packagedir}/$incoming" )" \
938 "$( stat -c "%y %n" "$1" )" \
940 return 6
943 esac
944 done
945 unset item
947 # Check package pre-existence
948 if test "$opt_force" != opt_force && \
949 test -e "$rootdir${packagedir}/$incoming"
950 then
951 warn \
952 "" \
953 " The package to be updated already exist;" \
954 "Unless the -f option is given, this package won't be UPGRADED."
955 return 6;
958 # Prepare the package to install it in a temporary location
960 # Set random directory using packagedir as prefix and 'incoming' as suffix
961 PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
963 # Pre-install the package in the custom 'packagedir'
965 save_packagedir="$rootdir${packagedir}"
966 packagedir="$PRVLOC"
968 echo "Pre-installing package in a temporary location ..."
969 opt_prune=opt_prune # Turn on prune operation.
970 _isUpgrade=_isUpgrade.on install_mode "$1" > /dev/null
971 _isUpgrade=_isUpgrade.off
973 # Restore variable before looking for old packages
974 packagedir=$save_packagedir
975 unset save_packagedir
977 echo "Looking for installations under the same name ..."
978 for long_name in "$rootdir${packagedir}/$(pkgbase $incoming)"*
980 found="${long_name##*/}"
982 # The search for the package to be deleted
983 # cannot be the same to the temporary location
984 test "$long_name" = "$PRVLOC" && continue;
986 fnmatch "$(pkgbase $found)*" "$incoming" || continue;
987 echo "${long_name}: Detected."
989 # A package directory is preserved if -k is given
990 delete_mode "$found" > /dev/null
991 done
992 unset long_name found
994 # Re-install the package removing the temporary location
996 install_mode "$1"
997 opt_prune=opt_prune.off # Turn off prune operation.
999 echo "Deleting temporary location ..."
1000 rm -rf -- "$PRVLOC" || chkstatus_or_exit
1001 echo "removed directory: '$PRVLOC'"
1003 echo ""
1004 echo "Successful upgrade to '${incoming}'."
1006 # Remove remaining variables
1007 unset incoming PRVLOC
1010 warn_mode()
1012 # Complain if the package cannot be well-read
1014 is_readable "$1" || exit 4
1016 # Complain if the package does not end in .tlz
1018 if ! fnmatch '*.tlz' "$1"
1019 then
1020 warn "\`${1}' does not end in .tlz"
1021 return 4
1024 # List content of files excluding directories
1025 while test -f "$1"
1027 tarlz -tvvf "$1" | awk '!/^drwx/'
1028 chkstatus_or_exit 3
1029 shift;
1030 done
1031 return 0
1034 extract_mode()
1036 # Perform sanity checks before package extraction
1038 is_readable "$1" || exit 4
1040 test -f "$1" || {
1041 warn "\`${1}' is not a regular file."
1042 return 4
1045 # Preparations to extract the package
1047 name=$(basename -- "$1" .tlz)
1049 # Set random directory using 'name' as prefix
1050 PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
1052 # Trap to remove 'PRVDIR' on disruptions
1053 trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
1055 # Create 'PRVDIR' removing access for all but user
1056 ( umask 077 ; mkdir -- $PRVDIR )
1057 mkdir -p -m 700 -- $PRVDIR
1059 echo "Extracting package $name ..."
1060 ( umask 000 ; cd -- $PRVDIR && tarlz -xvf - ) < "$1"
1061 if test $? -ne 0
1062 then
1063 # Try to remove (empty) 'PRVDIR' on failure
1064 rmdir -- $PRVDIR
1065 exit 3;
1067 echo "$name has been extracted on $PRVDIR"
1069 # Reset given signals
1070 trap - HUP INT ABRT TERM
1072 # Remove used variables
1073 unset name PRVDIR
1076 #### Extra functions used in the modes
1078 pkgbase()
1080 string=$(basename -- "$1" .tlz)
1082 # Match cases to print the package name.
1084 # We will take into account the four segments removing
1085 # the last two to print the package (long) name
1086 case $string in
1087 *-*-*+*)
1088 echo "${string%-*-*}"
1091 echo "$string"
1093 esac
1095 unset string
1098 unpack()
1100 for file in "$@"
1102 case $file in
1103 *.tar)
1104 tar -tf "$file" > /dev/null && \
1105 tar -xpf "$file"
1106 chkstatus_or_exit 3
1108 *.tar.gz | *.tgz | *.tar.Z )
1109 gzip -cd "$file" | tar -tf - > /dev/null && \
1110 gzip -cd "$file" | tar -xpf -
1111 chkstatus_or_exit 3
1113 *.tar.bz2 | *.tbz2 | *.tbz )
1114 bzip2 -cd "$file" | tar -tf - > /dev/null && \
1115 bzip2 -cd "$file" | tar -xpf -
1116 chkstatus_or_exit 3
1118 *.tar.lz | *.tlz )
1119 tarlz -tf "$file" > /dev/null && \
1120 tarlz -xf "$file"
1121 chkstatus_or_exit 3
1123 *.tar.xz | *.txz )
1124 xz -cd "$file" | tar -tf - > /dev/null && \
1125 xz -cd "$file" | tar -xpf -
1126 chkstatus_or_exit 3
1128 *.zip | *.ZIP )
1129 unzip -t "$file" > /dev/null && \
1130 unzip "$file" > /dev/null
1131 chkstatus_or_exit 3
1133 *.gz)
1134 gzip -t "$file" && \
1135 gzip -cd "$file" > "$(basename -- $file .gz)"
1136 chkstatus_or_exit 3
1138 *.Z)
1139 gzip -t "$file" && \
1140 gzip -cd "$file" > "$(basename -- $file .Z)"
1141 chkstatus_or_exit 3
1143 *.bz2)
1144 bzip2 -t "$file" && \
1145 bzip2 -cd "$file" > "$(basename -- $file .bz2)"
1146 chkstatus_or_exit 3
1148 *.lz)
1149 lzip -t "$file" && \
1150 lzip -cd "$file" > "$(basename -- $file .lz)"
1151 chkstatus_or_exit 3
1153 *.xz)
1154 xz -t "$file" && \
1155 xz -cd "$file" > "$(basename -- $file .xz)"
1156 chkstatus_or_exit 3
1159 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
1160 exit 1
1161 esac
1162 done
1163 unset file
1166 do_meta()
1168 # Extract information from the recipe to create the meta file.
1170 # The package description is pre-formatted in 78 columns,
1171 # the '#' character and a space is added as prefix to conform
1172 # 80 columns in total
1174 cat << EOF
1175 $(echo "$description" | fold -w 78 | awk '$0="# " $0')
1177 QICFLAGS="$QICFLAGS"
1178 QICXXFLAGS="$QICXXFLAGS"
1179 QILDFLAGS="$QILDFLAGS"
1180 program=$program
1181 version=$version
1182 release=$release
1183 blurb="$(echo "$description" | sed -e '/^$/d;2q')"
1184 homepage="$homepage"
1185 license="$license"
1186 fetch="$fetch"
1187 replace="$replace"
1192 #### Default values
1194 PROGRAM="${0##*/}"
1195 packagedir=@PACKAGEDIR@
1196 targetdir=@TARGETDIR@
1197 blacklist="perl graft tarlz plzip musl glibc"
1198 RC=RC
1199 RCFILE=@SYSCONFDIR@/qirc
1200 opt_install=opt_install.off
1201 opt_update=opt_update.off
1202 opt_force=opt_force.off
1203 opt_keep=opt_keep.off
1204 opt_incr_release=opt_incr_release.off
1205 opt_skipqsts=opt_skipqsts.off
1206 opt_nopkg=opt_nopkg.off
1207 opt_prune=opt_prune.off
1208 rootdir=""
1209 jobs=1
1210 mode=""
1211 verbose=0
1212 graft_v=-v
1213 graft_r=""
1214 _isUpgrade=_isUpgrade.off
1215 TMPDIR="${TMPDIR:=/usr/src/qi/build}"
1216 QICFLAGS="${QICFLAGS:=-g0 -Os}"
1217 QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
1218 QILDFLAGS="${QILDFLAGS:=-s}"
1219 worktree=/usr/src/qi
1220 tardir=${worktree}/sources
1221 outdir=/var/cache/qi/packages
1222 netget="wget -c -w1 -t3 --no-check-certificate"
1223 rsync="rsync -v -a -L -z -i --progress"
1224 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1225 infodir=@INFODIR@
1226 mandir=@MANDIR@
1227 docdir=@DOCDIR@
1229 # Store (default) directory locations
1230 QI_TARGETDIR=$targetdir
1231 QI_PACKAGEDIR=$packagedir
1232 QI_WORKTREE=$worktree
1233 QI_TARDIR=$tardir
1234 QI_OUTDIR=$outdir
1236 #### Parse options
1238 while getopts :bcdiouwxLNP:t:fkvO:W:Z:a:j:1nSpr:hV name
1240 case $name in
1242 if test -z "$mode"
1243 then
1244 readconfig
1245 mode=build_mode
1249 mode=create_mode
1252 readconfig
1253 mode=delete_mode
1256 if test -z "$mode"
1257 then
1258 readconfig
1259 mode=install_mode
1261 if test "$mode" = build_mode
1262 then
1263 opt_install=opt_install
1267 mode=resolve_mode
1270 if test -z "$mode"
1271 then
1272 readconfig
1273 mode=upgrade_mode
1275 if test "$mode" = build_mode
1276 then
1277 opt_update=opt_update
1281 mode=warn_mode
1284 mode=extract_mode
1287 printf "%s\n" \
1288 "QI_TARGETDIR=$QI_TARGETDIR" \
1289 "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
1290 "QI_WORKTREE=$QI_WORKTREE" \
1291 "QI_TARDIR=$QI_TARDIR" \
1292 "QI_OUTDIR=$QI_OUTDIR"
1293 exit 0
1296 RC=RC.off
1299 packagedir="$OPTARG"
1302 targetdir="$OPTARG"
1305 opt_force=opt_force
1308 opt_keep=opt_keep
1311 verbose=$(( verbose + 1 ))
1314 outdir="$OPTARG"
1317 worktree="$OPTARG"
1320 tardir="$OPTARG"
1323 arch="$OPTARG"
1326 jobs="$OPTARG"
1329 opt_incr_release=opt_incr_release
1332 opt_nopkg=opt_nopkg
1335 opt_skipqsts=opt_skipqsts
1338 opt_prune=opt_prune
1341 rootdir="$OPTARG"
1344 usage
1345 exit 0
1348 version
1349 exit 0
1352 warn "Option '-${OPTARG}' requires an argument"
1353 usage
1354 exit 1
1357 warn "Illegal option -- '-${OPTARG}'"
1358 usage
1359 exit 1
1361 esac
1362 done
1363 shift $(( OPTIND - 1 ))
1365 if test $# -eq 0
1366 then
1367 usage
1368 exit 1
1371 # Program sanity check
1372 for need in awk basename chmod cp dirname find fold graft grep \
1373 mkdir mktemp rm rmdir sed sha256sum stat tarlz
1375 if ! type $need 1> /dev/null 2> /dev/null
1376 then
1377 warn "${PROGRAM}: cannot operate without ${need}(1): Check your PATH"
1378 exit 2
1380 done
1381 unset need
1383 # Determine verbosity level/flag
1385 if test "$verbose" -gt 1
1386 then
1387 graft_v=-V
1390 # Read standard input if FILE is -, or when FILE
1391 # is not connected to a terminal.
1393 if test "$1" = - || test ! -t 0
1394 then
1395 # Unset positional parameters setting $# to zero
1396 set --
1398 # Assign remaining arguments to the positional parameters
1399 while read -r input
1401 set -- "$@" "$input"
1402 done
1405 # We need at least one operating mode
1406 if test -z "$mode"
1407 then
1408 usage
1409 exit 4
1412 umask 022; # Remove write permission for group and other.
1414 # Validate 'packagedir' and 'targetdir' as canonical directories
1416 # The single slash '/' does not qualify here
1417 if ! fnmatch '/?*' "$packagedir"
1418 then
1419 warn "${PROGRAM}: Package directory \`${packagedir}' is not fully qualified"
1420 exit 4
1422 if test ! -d "$packagedir"
1423 then
1424 warn "${PROGRAM}: Package directory \`${packagedir}' does not exist"
1425 exit 4
1428 # The single slash '/' is valid here
1429 if ! fnmatch '/*' "$targetdir"
1430 then
1431 warn "${PROGRAM}: Target directory \`${targetdir}' is not fully qualified"
1432 exit 4
1434 if test ! -d "$targetdir"
1435 then
1436 warn "${PROGRAM}: Target directory \`${targetdir}' does not exist"
1437 exit 4
1440 # Validate 'rootdir' directory
1442 if test -n "$rootdir"
1443 then
1444 if test -d "$rootdir" && test "$rootdir" != /
1445 then
1446 # Remove slash from the end
1447 rootdir="${rootdir%/}"
1449 # A workaround for graft-2.13+. The specified directory is
1450 # relative to the log file, we prepend it inside the rootdir
1452 eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
1453 mkdir -p -- "$rootdir$(dirname -- $GRAFT_LOGFILE)" || chkstatus_or_exit
1455 # Compose 'rootdir' and log file option to be used with graft(1)
1456 graft_r="-r $rootdir -l $GRAFT_LOGFILE"
1458 # Unset variables coming from eval
1459 unset GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
1460 else
1461 warn "${PROGRAM}: Root directory \`${rootdir}' is not fully qualified"
1462 exit 4
1464 export rootdir
1467 # Ensure 'TMPDIR' creation to prefix temporary files
1469 if test ! -d "$TMPDIR"
1470 then
1471 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1473 readonly TMPDIR
1475 # Process each package or recipe provided on the command-line
1477 for package in "$@"
1479 $mode $package
1480 done