qi: check the exit status of commands
[dragora.git] / qi / src / qi.in
blob7cd703cb41fe08ba3d577d5fa30cde47ab89241b
1 #! /bin/sh -
2 # Copyright (C) 2016-2017 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" \
43 " -x extract packages for debugging purposes" \
44 "" \
45 "Common options:" \
46 " -N don't read the configuration file" \
47 " -P <DIR> directory for package (de)installation;" \
48 " only valid for -i, -d, -or -u options" \
49 " -T <DIR> target directory for symbolic links;" \
50 " only valid for -i, -d, or -u options" \
51 " -f force rebuild the package of a recipe," \
52 " force the removal of a package on CONFLICT;" \
53 " only valid for -b and -d options" \
54 " -k keep (don't delete) srcdir or destdir" \
55 " from the build mode" \
56 " keep (don't delete) package directories" \
57 " from a removal, this also keeps previous" \
58 " package names from an upgrade" \
59 " -v be verbose (a 2nd -v gives more)" \
60 "" \
61 "Options for 'build' mode (-b):" \
62 " -O <DIR> where the produced packages are written" \
63 " -W <DIR> where archives, patches, and recipes are expected" \
64 " -Z <DIR> where the sources will be found" \
65 " -a architecture to use" \
66 " -j parallel jobs for the compiler" \
67 " -1 increment release number (release + 1)" \
68 " -n don't create a .tlz package" \
69 "" \
70 "Other options:" \
71 " -h display this help and exit" \
72 " -V output version information" \
73 "" \
74 "Some influential environment variables:" \
75 " TMPDIR temporary directory for sources" \
76 " QICFLAGS C compiler flags" \
77 " QICXXFLAGS C++ compiler flags" \
78 " QILDFLAGS linker flags" \
79 "" \
80 "When FILE is -, read standard input." \
84 version()
86 printf "%s\n" \
87 "$PROGRAM @VERSION@" \
88 "Copyright (C) 2016-2017 Matias Andres Fonzo." \
89 "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
90 "This is free software: you are free to change and redistribute it." \
91 "There is NO WARRANTY, to the extent permitted by law."
94 warn()
96 printf "%s\n" "$@" 1>&2
99 is_readable()
101 if test -e "$1"
102 then
103 if ! test -r "$1"
104 then
105 echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
106 return 1
108 else
109 echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
110 return 1
112 return 0
115 # Determine whether $2 matches pattern $1
116 fnmatch()
118 case $2 in
120 return 0
123 return 1
125 esac
128 chkstatus_or_exit()
130 status=$?
132 if test $status -ne 0
133 then
134 echo "Return status = $status" 1>&2
135 exit ${1-2}; # If not given, defaults to 2
138 unset status
141 readconfig()
143 if test $RC = RC
144 then
145 is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
147 echo "Processing \`${RCFILE}' ..."
149 test -f "$RCFILE" || {
150 warn "${RCFILE} is not a regular file."
151 return 1
154 # Parse config file
155 while IFS='=' read -r variable value
157 case $variable in
158 \#* | "") # Ignore commented or blank lines
159 continue
161 esac
163 # Set variable name avoiding code execution
164 eval "$variable=\${value}"
165 done < "$RCFILE"
169 #### Mode functions
171 build_mode()
173 recipe=$1
175 # A recipe is any valid regular file, the current working directory
176 # has priority over the working tree (or where the recipes reside).
177 # The 'worktree' is the second place where to find a recipe. Also,
178 # we complete the possibility of using the directory name to invoke
179 # a recipe if it contains "recipe" as the file name.
181 if test ! -f "$recipe"
182 then
183 if test -f "${PWD}/${recipe}/recipe"
184 then
185 recipe="${PWD}/${recipe}/recipe"
186 elif test -f "${worktree}/$recipe"
187 then
188 recipe="${worktree}/$recipe"
189 elif test -f "${worktree}/${recipe}/recipe"
190 then
191 recipe="${worktree}/${recipe}/recipe"
192 elif test -f "${worktree}/recipes/${recipe}"
193 then
194 recipe="${worktree}/recipes/${recipe}"
195 elif test -f "${worktree}/recipes/${recipe}/recipe"
196 then
197 recipe="${worktree}/recipes/${recipe}/recipe"
201 # Perform sanity checks
203 test -f "$recipe" || {
204 warn "${PROGRAM}: \`${recipe}' is not a regular file."
205 exit 4
208 # Start preparations to import the recipe
210 # Separate the directory name from the filename,
211 # getting its absolute path name and base name
213 CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
214 recipe=$(basename -- "$recipe")
216 # Check readability for load the recipe on success
218 is_readable "${CWD}/$recipe" || exit 4
220 # Make tail directories if needed
221 test -d "${worktree}/archive" || mkdir -p -- "${worktree}/archive"
222 test -d "${worktree}/patches" || mkdir -p -- "${worktree}/patches"
223 test -d "${worktree}/recipes" || mkdir -p -- "${worktree}/recipes"
224 test -d "$tardir" || mkdir -p -- "$tardir"
226 # Variables treatment for the actual and the next recipe,
227 # if there are more than one to process.
229 # Unset special variables that can only be predefined
230 # in the recipe not coming from ${sysconfdir}/qirc
232 unset srcdir destdir pkgname pkgversion program version release \
233 fetch description homepage license replace keep_srcdir
235 # The following variables must be restored later
236 save_arch="${save_arch:=$arch}"
237 save_jobs="${save_jobs:=$jobs}"
239 unset arch jobs
241 # The following variables cannot be redefined in the recipe
242 readonly outdir worktree netget rsync
244 # Recommended practices is to set variables in front of `configure'
245 # or in front of make(1). Refer to the documentation of Qi.
247 unset CFLAGS CXXFLAGS LDFLAGS
249 # Load the recipe
251 echo "Loading ${CWD}/$recipe ..."
252 . "${CWD}/$recipe"
254 # Check for required variables
255 if test -z "$program"
256 then
257 warn "${recipe}: The variable 'program' is not defined."
258 exit 5
260 if test -z "$version"
261 then
262 warn "${recipe}: The variable 'version' is not defined."
263 exit 5
265 if test -z "$release"
266 then
267 warn "${recipe}: The variable 'release' is not defined."
268 exit 5
271 # Pre-settings before to start building
273 # Increment the release number if the option was given
274 if test "$opt_incr_release" = opt_incr_release
275 then
276 release=$(( release + 1 ))
279 # Allow the dot as definition for 'tardir'
280 if test "$tardir" = .
281 then
282 tardir="$CWD"
285 # Set default values for the following special variables
287 pkgname="${pkgname:=$program}"
288 pkgversion="${pkgversion:=$version}"
289 srcdir="${srcdir:=${program}-$version}"
290 destdir="${destdir:=${TMPDIR}/package-$pkgname}"
292 # Restore default values if they were not given in the recipe
293 arch="${arch:=$save_arch}"
294 jobs="${jobs:=$save_jobs}"
296 # Complete package name adding "pkgversion-arch+release"
297 full_pkgname=$pkgname-${pkgversion}-${arch}+${release}
299 # If a package is going to be created, the existence of a
300 # previous build of the package is detected and reported.
301 # Under normal conditions, the recipe is built as long as
302 # it is newer than the package produced; if not, we warn
303 # the user about it. Rebuilding the package is also
304 # possible (through the force ;-)
306 if test "$opt_nopkg" != opt_nopkg && \
307 { test "$opt_force" != opt_force && \
308 test -r "${outdir}/${full_pkgname}.tlz" ; }
309 then
310 if test "${CWD}/$recipe" -nt "${outdir}/${full_pkgname}.tlz"
311 then
312 warn \
313 "" \
314 "The recipe is newer than the detected package:" \
315 "" \
316 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
317 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
318 "" \
319 " The recipe will be processed." \
321 elif test "$_opt_ignoreqsts" = _opt_ignoreqsts
322 then
323 warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
324 return 0
325 else
326 warn \
327 "" \
328 "The recipe already produced the package:" \
329 "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
330 "" \
331 "But the recipe is still older than the produced package:" \
332 "$( stat -c "%y %n" "${CWD}/$recipe" )" \
333 "" \
334 " Probably nothing has changed." \
337 # In non-interactive mode, the user is asked about
338 # rebuilding the package. In interactive mode,
339 # the user need to pass the option explicitly.
341 if test ! -t 0
342 then
343 printf "%b" \
344 "Do you want to rebuild this package?\n" \
345 "1) Yes, built it\n" \
346 "2) No, skipt it [default]\n" \
347 "3) Resume, skipping completed recipes\n" \
348 "Choose an option number: " > /dev/tty
349 IFS= read -r REPLY < /dev/tty || chkstatus_or_exit
350 case $REPLY in
352 echo "$REPLY" > /dev/tty
353 unset REPLY
356 unset REPLY
357 echo "Building unprocessed and/or modified recipes ..." > /dev/tty
358 echo ""
359 _opt_ignoreqsts=_opt_ignoreqsts
360 readonly _opt_ignoreqsts
361 return 0
364 unset REPLY
365 echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
366 echo ""
367 return 0
369 esac
370 else
371 warn "( Use '-f' to re-process ${CWD}/$recipe )" ""
372 return 6;
377 # Fetch remote sources
379 echo "Fetching remote sources if needed ..."
380 if test -n "$fetch"
381 then
382 for origin in $fetch
384 _source="${origin##*/}"
386 echo "Looking for ${tardir}/$_source ..."
387 test -e "${tardir}/$_source" && continue
389 # Download the source if does not exist
391 warn "Cannot find $_source in $tardir:" \
392 "attempting to get it from ${origin%/*} ..."
394 case $origin in
395 rsync://*)
397 cd -- "$tardir" && $rsync $origin && \
398 sha256sum $_source > ${_source}.sha256
401 *://*)
403 cd -- "$tardir" && $netget $origin && \
404 sha256sum $_source > ${_source}.sha256
408 warn "${PROGRAM}: Unrecognized protocol for $origin"
409 exit 4
410 esac
411 done
412 unset origin _source
413 else
414 warn "The variable 'fetch' is empty."
417 # Prepare special directories for build the source,
418 # the destination and the output of the package
420 echo "Preparing directories ..."
422 if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
423 then
424 rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
425 echo "removed directory: '${TMPDIR}/$srcdir'"
427 mkdir -p -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
429 if test -d "$destdir"
430 then
431 rm -rf -- "$destdir" || chkstatus_or_exit
432 echo "removed directory: '$destdir'"
434 mkdir -p -- "$destdir" || chkstatus_or_exit
435 echo "mkdir: created directory '$destdir'"
437 if test ! -d "$outdir"
438 then
439 mkdir -p -- "$outdir" || chkstatus_or_exit
440 echo "mkdir: created directory '$outdir'"
443 # Set sane permissions for '${TMPDIR}/$srcdir'
444 echo "${TMPDIR}/${srcdir}: Changing file mode bits to u+w,go-w,a+rX-s ..."
445 chmod -R u+w,go-w,a+rX-s "${TMPDIR}/$srcdir" || chkstatus_or_exit
447 echo "Entering to 'TMPDIR': $TMPDIR ..."
448 cd -- "$TMPDIR" || chkstatus_or_exit
450 # Set trap before to run the build() function in order
451 # to catch the return status, exit code 2 if fails
453 trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
455 echo "Running build() ..."
456 build
458 # Restore shell option(s)
459 set +e ; set +x
461 # Reset given signals
462 trap - EXIT HUP INT QUIT ABRT TERM
464 unset build
466 # If 'destdir' is empty, the package won't be created
467 if rmdir -- "$destdir" 2> /dev/null
468 then
469 warn "${full_pkgname}.tlz won't be created: 'destdir' was empty."
470 opt_nopkg=opt_nopkg
473 # Create (make) the package
475 if test "$opt_nopkg" != opt_nopkg
476 then
477 # Edit the recipe when 'release' is incremented
478 if test "$opt_incr_release" = opt_incr_release
479 then
480 echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
481 ed "${CWD}/$recipe" || chkstatus_or_exit
484 mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
486 # Include a recipe copy into the package
487 cp -p "${CWD}/$recipe" \
488 "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
489 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
491 # Detect post-install script for inclusion
493 if test -f "${CWD}/post-install"
494 then
495 echo "${CWD}/post-install: Detected."
496 cp -p "${CWD}/post-install" \
497 "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
498 chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
501 # Detect declared package names for later replacement
503 if test -n "$replace"
504 then
505 warn \
506 "The following package names has been declared for replacement:" \
507 " $replace"
509 rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
510 for item in $replace
512 echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
513 done
514 unset item
517 # Create meta file for the package information
518 echo "Creating meta file ${full_pkgname}.tlz.txt ..."
519 do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
521 # Make a copy of it for the database
522 cp -p "${outdir}/${full_pkgname}.tlz.txt" \
523 "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
525 # Produce the package
526 cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
529 # Back to the current working directory
530 cd -- "$CWD" || chkstatus_or_exit
532 # Delete 'srcdir' or 'destdir' if -k is not given
533 if test "$opt_keep" != opt_keep
534 then
535 echo "Deleting temporary directories ..."
537 if test -z "$keep_srcdir"
538 then
539 rm -rf -- "${TMPDIR}/${srcdir%%/*}" || chkstatus_or_exit
540 echo "removed directory: '${TMPDIR}/${srcdir%%/*}'"
541 else
542 warn "The variable 'keep_srcdir' is set:" \
543 "'${TMPDIR}/${srcdir%%/*}' will not be deleted."
546 if test -d "$destdir"
547 then
548 rm -rf -- "$destdir" || chkstatus_or_exit
549 echo "removed directory: '$destdir'"
553 # Install or update the package if -i or -u was passed
554 if test "$opt_install" = opt_install && test "$opt_nopkg" != opt_nopkg
555 then
556 install_mode "${outdir}/${full_pkgname}.tlz"
557 elif test "$opt_update" = opt_update && test "$opt_nopkg" != opt_nopkg
558 then
559 upgrade_mode "${outdir}/${full_pkgname}.tlz"
562 echo ""
563 echo "All done for ${CWD}/${recipe}."
564 echo ""
567 create_mode()
569 directory=$(dirname -- "$1")
570 name=$(basename -- "$1")
572 # Perform sanity checks
574 is_readable "$directory" || exit 4
576 # Check again to find out if it is a valid directory
577 test -d "$directory" || {
578 warn "${PROGRAM}: Package directory '$name' does not exist."
579 exit 4
582 test "$directory" = . && {
583 warn "${PROGRAM}: Cannot create package on current working directory."
584 exit 4
587 test "$name" = "${name%.tlz}" && {
588 warn \
589 "${PROGRAM}: Package format '$name' not supported." \
590 "It should be \`name-version-architecture+release.tlz'"
591 exit 4
594 echo "${directory}: Creating package $name ..."
595 ( umask 022 ; tar cvf - ./* | lzip -9cvv ) > "${directory}/$name"
596 chkstatus_or_exit 3
598 echo "${directory}/$name: Creating SHA256 checksum ..."
599 ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
601 # Remove used variables
602 unset directory name
605 delete_mode()
607 expunge=$1
609 # If package is not a fully qualified directory, prepend it with
610 # the value of packagedir. The solely '/' does not qualify here.
612 if ! fnmatch '/?*' "$expunge"
613 then
614 expunge=$(basename -- "$expunge" .tlz)
615 expunge="${packagedir}/$expunge"
618 # Complain if the package directory does not exist
620 test -d "$expunge" || {
621 warn "${PROGRAM}: Package directory '$expunge' does not exist."
622 exit 4
625 # Complain if the package directory cannot be well-read
627 is_readable "$expunge" || exit 4
629 # Scan for possible conflicts, stop if arise
630 if test "$opt_force" != opt_force
631 then
632 echo "Checking for possible conflicts ..."
633 graft -d -n -t "$targetdir" "$expunge" > /dev/null
634 chkstatus_or_exit 6
637 # Ignore some signals up to completing the (de)installation
638 trap "" HUP INT QUIT ABRT TERM
640 # Remove package from Graft control
642 # Remove objects (files, links or directories) from the target
643 # directory that are in conflict with the package directory
645 echo "Pruning any conflict ..."
646 graft -p -D -u -t "$targetdir" "$expunge"
647 chkstatus_or_exit 2
649 echo "Disabling links ..."
650 graft -d -D -u $graft_v -t "$targetdir" "$expunge"
651 chkstatus_or_exit 2
653 # Delete package directory
654 if test "$opt_keep" != opt_keep
655 then
656 warn "Deleting package directory, if present ..."
658 if test -d "$expunge"
659 then
660 rm -rf -- "$expunge" || chkstatus_or_exit
661 echo "removed directory: '$expunge'"
662 else
663 warn "Package directory '$expunge' is no longer there."
667 # Reset given signals
668 trap - HUP INT QUIT ABRT TERM
670 # Remove used variables
671 unset expunge
674 install_mode()
676 # Complain if the package cannot be well-read
678 is_readable "$1" || exit 4
680 # Complain if the package does not end in .tlz
682 if ! fnmatch '*.tlz' "$1"
683 then
684 warn "${PROGRAM}: \`${1}' does not end in .tlz"
685 exit 4
688 # Make preparations to install the package
690 # Get the filename
691 name=$(basename -- "$1" .tlz)
693 echo "Checking tarball integrity ..."
694 tar -tf "$1" > /dev/null
695 chkstatus_or_exit 3
697 # Create package directory using 'name'
698 if ! test -d "${packagedir}/$name"
699 then
700 mkdir -p -- "${packagedir}/$name" || chkstatus_or_exit
701 echo "mkdir: created directory '${packagedir}/$name'"
704 # Prepare to decompress the package
706 # Ignore some signals up to completing the installation
707 trap "" HUP INT QUIT ABRT TERM
709 echo "Decompressing $1 ..."
710 ( cd -- "${packagedir}/$name" && lzip -cd - | tar xpf - ) < "$1"
711 chkstatus_or_exit 3
713 # Transite package to Graft control
715 # Remove objects (files, links or directories) from the target
716 # directory that are in conflict with the package directory
718 echo "Pruning any conflict ..."
719 graft -p -D -u -t "$targetdir" "${packagedir}/$name"
720 chkstatus_or_exit 2
722 echo "Enabling symbolic links ..."
723 graft -i -P $graft_v -t "$targetdir" "${packagedir}/$name"
724 chkstatus_or_exit 2
726 # Show package description
728 if test -r "${packagedir}/${name}/var/lib/qi/${name}.txt"
729 then
730 grep '^#' "${packagedir}/${name}/var/lib/qi/${name}.txt"
731 elif test -r "${1}.txt"
732 then
733 # From the external meta file
734 grep '^#' "${1}.txt"
735 else
736 warn "Description file not found for '$name'."
739 # Check and run the post-install script if exist
741 if test -r "${packagedir}/${name}/var/lib/qi/${name}.sh"
742 then
743 echo "Running post-install script for \`${name}' ..."
745 cd -- "${targetdir}"/ && /bin/sh \
746 "${packagedir}/${name}/var/lib/qi/${name}.sh"
750 # Check if there are declared package names for replacement
752 if test -r "${packagedir}/${name}/var/lib/qi/${name}.replace"
753 then
754 short_name=$(pkgbase $name)
756 while read -r line
758 for replace in "${packagedir}/${line}"*
760 # The search of the package to replace cannot be
761 # the same to the incoming package on the installation
762 test "$(pkgbase $replace)" = "$short_name" && continue;
764 warn "Replacing package \`${replace}' ..."
766 opt_force=opt_force opt_keep=opt_keep.off \
767 delete_mode "$replace" > /dev/null
768 done
769 done < "${packagedir}/${name}/var/lib/qi/${name}.replace"
771 unset short_name
774 # Reset given signals
775 trap - HUP INT QUIT ABRT TERM
777 # Remove used variables
778 unset name
781 resolve_mode() {
782 # Complain if the file cannot be well-read
784 is_readable "$1" || exit 4
786 # Complain if the file does not end in .order
788 if ! fnmatch '*.order' "$1"
789 then
790 warn "${PROGRAM}: \`${1}' does not end in .order"
791 exit 4
794 # Get a clean list of the file while prints its content in reverse order,
795 # lines containing: colons, comments, parentheses, end of line, and blank
796 # lines, are removed. The parentheses are used to insert a reference.
797 # The last `awk' in the pipe: removes nonconsecutive lines, duplicate.
798 awk \
799 '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
800 "$1" | awk '!s[$0]++'
803 upgrade_mode()
805 # Complain if the package is not a regular file
807 test -f "$1" || {
808 warn "${PROGRAM}: \`${1}' is not a regular file."
809 exit 4
812 # Get the filename
813 incoming=$(basename -- "$1" .tlz)
815 # Install the package, first
816 install_mode "$1"
818 # Remove old package names; the package directory
819 # is preserved if the option -k is not given
821 echo "Searching for previous installations under the same package name ..."
822 short_name=$(pkgbase "$incoming")
824 for long_name in "${packagedir}/$short_name"*
826 # The search of the package to remove cannot be
827 # the same to the incoming package on the upgrade
828 if test "$incoming" != "${long_name##*/}"
829 then
830 echo "${long_name}: Detected."
832 if test "$opt_keep" != opt_keep
833 then
834 if test -d "$long_name"
835 then
836 rm -rf -- "$long_name" || chkstatus_or_exit
837 echo "removed directory: '$long_name'"
838 else
839 warn "Package directory '$long_name' is no longer there."
843 done
845 # Remove used variables
846 unset incoming short_name long_name
849 extract_mode()
851 # Perform sanity checks before package extraction
853 is_readable "$1" || exit 4
855 test -f "$1" || {
856 warn "${PROGRAM}: \`${1}' is not a regular file."
857 exit 4
860 # Preparations to extract the package
862 name=$(basename -- "$1" .tlz)
864 # Set random directory using 'name' as prefix
865 PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
867 # Trap to remove 'PRVDIR' on disruptions
868 trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
870 # Create 'PRVDIR' removing access for all but user
871 ( umask 077 ; mkdir -- $PRVDIR )
872 mkdir -p -m 700 -- $PRVDIR
874 # Extract the package
876 echo "Extracting package $name on $PRVDIR ..."
877 ( umask 000 ; cd -- $PRVDIR && lzip -cd - | tar xf - ) < "$1"
878 if test $? -ne 0
879 then
880 # Try to remove (empty) 'PRVDIR' on failure
881 rmdir -- $PRVDIR
882 exit 3;
885 # Reset given signals
886 trap - HUP INT ABRT TERM
888 # Remove used variables
889 unset name PRVDIR
892 #### Extra functions used in the modes
894 pkgbase()
896 string=$(basename -- "$1" .tlz)
898 # Take and count the dashes only
900 dashes=$(echo "$string" | tr -cd '-')
901 dashes="${#dashes}"
903 # Match cases to print the package name
904 case $dashes in
905 0 | "")
906 # NAME or null
907 echo "$string"
910 # NAME-VERSION
911 echo "${string%-*}"
913 2 | 3)
914 # NAME_[-LONG]-VERSION-ARCH+release
915 echo "${string%-*-*}"
918 # NAME_[-LONG][-VERYLONG]-VERSION-ARCH+release
919 echo "$string" | cut -f 1-$((dashes - 2)) -d -
921 esac
923 unset string dashes
926 unpack()
928 for file
930 case $file in
931 *.tar | *.tar.* | *.t[gblx]z* )
932 tar tf "$file" > /dev/null && tar xf "$file"
933 chkstatus_or_exit 3
935 *.zip | *.ZIP )
936 unzip -t "$file" && unzip "$file"
937 chkstatus_or_exit 3
939 *.gz)
940 gzip -t "$file" && gzip -cd "$file" > $(basename -- "$file" .gz)
941 chkstatus_or_exit 3
943 *.bz2)
944 bzip2 -t "$file" && bzip2 -cd "$file" > $(basename -- "$file" .bz2)
945 chkstatus_or_exit 3
947 *.lz)
948 lzip -t "$file" && lzip -cd "$file" > $(basename -- "$file" .lz)
949 chkstatus_or_exit 3
952 warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
953 exit 1
954 esac
955 done
956 unset file
959 do_meta()
961 # Extract information from the recipe to create the meta file.
963 # The package description is pre-formatted in 78 columns,
964 # the '#' character and a space is added as prefix to conform
965 # 80 columns in total
967 cat << EOF
968 $(echo "$description" | fold -w 78 | awk '$0="# " $0')
970 QICFLAGS="$QICFLAGS"
971 QICXXFLAGS="$QICXXFLAGS"
972 QILDFLAGS="$QILDFLAGS"
973 program=$program
974 version=$version
975 release=$release
976 blurb="$(echo "$description" | sed -e '/^$/d;2q')"
977 homepage="$homepage"
978 license="$license"
979 fetch="$fetch"
980 replace="$replace"
985 #### Default values
987 PROGRAM="${0##*/}"
988 packagedir=@PACKAGEDIR@
989 targetdir=@TARGETDIR@
990 RC=RC
991 RCFILE=@SYSCONFDIR@/qirc
992 opt_update=""
993 opt_force=opt_force.off
994 opt_keep=opt_keep.off
995 opt_incr_release=opt_incr_release.off
996 opt_nopkg=opt_nopkg.off
997 arch="$(uname -m)" || chkstatus_or_exit
998 jobs=1
999 mode=""
1000 verbose=0
1001 graft_v=-v
1002 _opt_ignoreqsts=_opt_ignoreqsts.off
1003 TMPDIR="${TMPDIR:=/usr/src/qi/build}"
1004 QICFLAGS="${QICFLAGS:=-g0 -Os}"
1005 QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
1006 QILDFLAGS="${QILDFLAGS:=-s}"
1007 worktree=/usr/src/qi
1008 tardir=${worktree}/sources
1009 outdir=/var/cache/qi/packages
1010 netget="wget -c -w1 -t3 --no-check-certificate"
1011 rsync="rsync -v -a -L -z -i --progress"
1012 configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
1013 infodir=@INFODIR@
1014 mandir=@MANDIR@
1015 docdir=@DOCDIR@
1017 #### Parse options
1019 while getopts :bcdiouxNP:T:fkvO:W:Z:a:j:1nhV name
1021 case $name in
1023 if test -z "$mode"
1024 then
1025 readconfig
1026 mode=build_mode
1030 mode=create_mode
1033 readconfig
1034 mode=delete_mode
1037 if test -z "$mode"
1038 then
1039 readconfig
1040 mode=install_mode
1042 if test "$mode" = build_mode
1043 then
1044 opt_install=opt_install
1048 mode=resolve_mode
1051 if test -z "$mode"
1052 then
1053 readconfig
1054 mode=upgrade_mode
1056 if test "$mode" = build_mode
1057 then
1058 opt_update=opt_update
1062 mode=extract_mode
1065 RC=RC.off
1068 packagedir="$OPTARG"
1071 targetdir="$OPTARG"
1074 opt_force=opt_force
1077 opt_keep=opt_keep
1080 verbose=$(( verbose + 1 ))
1083 outdir="$OPTARG"
1086 worktree="$OPTARG"
1089 tardir="$OPTARG"
1092 arch="$OPTARG"
1095 jobs="$OPTARG"
1098 opt_incr_release=opt_incr_release
1101 opt_nopkg=opt_nopkg
1104 usage
1105 exit 0
1108 version
1109 exit 0
1112 warn "Option '-${OPTARG}' requires an argument"
1113 usage
1114 exit 1
1117 warn "Illegal option -- '-${OPTARG}'"
1118 usage
1119 exit 1
1121 esac
1122 done
1123 shift $(( OPTIND - 1 ))
1125 if test $# -eq 0
1126 then
1127 usage
1128 exit 1
1131 ## Program sanity check
1132 for need in awk basename bzip2 chmod cp dirname fold graft grep gzip \
1133 lzip mkdir mktemp rm rmdir sed sha256sum stat tar unzip
1135 if ! type $need 1> /dev/null 2> /dev/null
1136 then
1137 warn "${PROGRAM}: cannot operate without ${need}(1): Check your PATH"
1138 exit 2
1140 done
1141 unset need
1143 # Determine verbosity level/flag
1145 if test "$verbose" -gt 1
1146 then
1147 graft_v=-V
1150 # Read standard input if FILE is -, or when FILE
1151 # is not connected to a terminal.
1153 if test "$1" = - || test ! -t 0
1154 then
1155 # Unset positional parameters setting $# to zero
1156 set --
1158 # Assign remaining arguments to the positional parameters
1159 while read -r input
1161 set -- "$@" "$input"
1162 done
1165 # We need at least one operating mode
1166 if test -z "$mode"
1167 then
1168 usage
1169 exit 4
1172 # Ensure 'TMPDIR' creation to prefix temporary files
1174 if test ! -d "$TMPDIR"
1175 then
1176 mkdir -p -- "$TMPDIR" || chkstatus_or_exit
1178 readonly TMPDIR
1180 # Remove write permission for group and other
1181 umask 022
1183 # Process each package or recipe provided on the command-line
1185 for package in "$@"
1187 $mode "$package"
1188 done