12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397 |
- #! /bin/sh -
- # Copyright (C) 2016-2018 Matias Fonzo <selk@dragora.org>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- # EXIT STATUS
- # 0 = Successful completion
- # 1 = Minor common errors (e.g: help usage, support not available)
- # 2 = Command execution error
- # 3 = Integrity check error for compressed files
- # 4 = File empty, not regular, or expected
- # 5 = Empty or not defined variable
- # 6 = Package already installed
- # 10 = Network manager error
- #### Functions
- usage()
- {
- printf "%s\n" \
- "Qi - A practical and user-friendly package manager." \
- "" \
- "Usage: $PROGRAM [OPTION...] [FILE]..." \
- "" \
- "Operation mode:" \
- " -b Build packages using recipes" \
- " -c Create .tlz package from directory" \
- " -d Delete packages" \
- " -i Install packages" \
- " -o Resolve build order through .order files" \
- " -u Update packages (implies -i, -d with -p)" \
- " -x Extract packages for debugging purposes" \
- "" \
- "Common options:" \
- " -L Print and exit default directory locations" \
- " -N Don't read the configuration file" \
- " -P <DIR> Package directory for (de)installations;" \
- " only valid for -i, -d, or -u options" \
- " -f Force package upgrade or force to proceed" \
- " with a recipe" \
- " -t <DIR> Target directory for symbolic links;" \
- " only valid for -i, -d, or -u options" \
- " -k Keep (don't delete) srcdir or destdir" \
- " Keep (don't delete) package directory" \
- " this is for -b, -d or -u options" \
- " -p Prune conflicts on package (de)installation" \
- " -r Use the named directory as the root directory" \
- " for installing, deleting, or upgrading packages." \
- " The target directory, package directory will be" \
- " relative to this specific directory" \
- " -v Be verbose (a 2nd -v gives more)" \
- "" \
- "Options for 'build' mode (-b):" \
- " -O <DIR> Where the produced packages are written" \
- " -W <DIR> Where archives, patches, and recipes are expected" \
- " -Z <DIR> Where (compressed) sources will be found" \
- " -a Architecture to use [detected]" \
- " -j Parallel jobs for the compiler" \
- " -1 Increment release number (release + 1)" \
- " -3 Resume skipping completed recipes option" \
- " -n Don't create a .tlz package" \
- "" \
- "Other options:" \
- " -h Display this help and exit" \
- " -V Output version information" \
- "" \
- "Some influential environment variables:" \
- " TMPDIR Temporary directory for sources" \
- " QICFLAGS C compiler flags" \
- " QICXXFLAGS C++ compiler flags" \
- " QILDFLAGS Linker flags" \
- "" \
- "When FILE is -, read standard input." \
- ""
- }
- version()
- {
- printf "%s\n" \
- "$PROGRAM @VERSION@" \
- "Copyright (C) 2016-2018 Matias Andres Fonzo." \
- "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
- "This is free software: you are free to change and redistribute it." \
- "There is NO WARRANTY, to the extent permitted by law."
- }
- warn()
- {
- printf "%s\n" "$@" 1>&2
- }
- is_readable()
- {
- if test -e "$1"
- then
- if ! test -r "$1"
- then
- echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
- return 1
- fi
- else
- echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
- return 1
- fi
- }
- # Portable alternative to the file operator -nt (among shells)
- is_newer()
- {
- if test -n "$(find $1 -prune -newer $2 -print)"
- then
- return 0
- fi
- return 1
- }
- # Determine whether $2 matches pattern $1
- fnmatch()
- {
- case $2 in
- $1)
- return 0
- ;;
- *)
- return 1
- ;;
- esac
- }
- chkstatus_or_exit()
- {
- status=$?
- if test $status -ne 0
- then
- echo "Return status = $status" 1>&2
- exit ${1-2}; # If not given, defaults to 2
- fi
- unset status
- }
- readconfig()
- {
- if test $RC = RC
- then
- is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
- echo "Processing \`${RCFILE}' ..."
- test -f "$RCFILE" || {
- warn "${RCFILE} is not a regular file."
- return 1
- }
- # Parse config file
- while IFS='=' read -r variable value
- do
- case $variable in
- \#* | "") # Ignore commented or blank lines
- continue
- ;;
- esac
- # Set variable name avoiding code execution
- eval "$variable=\${value}"
- done < "$RCFILE"
- fi
- }
- #### Mode functions
- build_mode()
- {
- recipe=$1
- # A recipe is any valid regular file, the current working directory
- # has priority over the working tree (or where the recipes reside).
- # The 'worktree' is the second place where to find a recipe. Also,
- # we complete the possibility of using the directory name to invoke
- # a recipe if it contains "recipe" as valid file name.
- if test ! -f "$recipe"
- then
- if test -f "${recipe}/recipe"
- then
- recipe="${recipe}/recipe"
- elif test -f "${worktree}/recipes/${recipe}/recipe"
- then
- recipe="${worktree}/recipes/${recipe}/recipe"
- fi
- fi
- test -f "$recipe" || {
- warn "\`${recipe}' is not a regular file."
- return 4
- }
- # Complain if the file name is not "recipe"
- if test "${recipe##*/}" = recipe
- then
- true
- else
- warn "\`${recipe}' is not a valid recipe name."
- return 4
- fi
- # Start preparations to import the recipe
- # Separate the directory name from the file name,
- # getting its absolute path and base name
- CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
- recipe=$(basename -- "$recipe")
- # Check readability for load the recipe on success
- is_readable "${CWD}/$recipe" || exit 4
- # Find target architecture if 'arch' is not set
- if test -z "$arch"
- then
- arch=$(${CC:-cc} -dumpmachine 2> /dev/null) || arch=unknown
- arch="${arch%%-*}" # Get the rid of target triplet.
- fi
- # Re-create external directories
- mkdir -p -- "${worktree}/archive" \
- "${worktree}/patches" \
- "${worktree}/recipes" \
- "$tardir"
- # Variables treatment for the current and next recipe.
- #
- # Unset special variables that can only be predefined on
- # the recipe and not coming from ${sysconfdir}/qirc
- unset srcdir destdir pkgname pkgversion program version release \
- fetch description homepage license replace full_pkgname \
- CFLAGS CXXFLAGS LDFLAGS
- # The following variables must be saved and restored
- save_arch="${save_arch:=$arch}"
- save_jobs="${save_jobs:=$jobs}"
- save_outdir="${save_outdir:=$outdir}"
- save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
- # Reset variable values in case of return
- arch=$save_arch
- jobs=$save_jobs
- outdir=$save_outdir
- opt_nopkg=$save_opt_nopkg
- # The following variables cannot be redefined on the recipe
- readonly worktree netget rsync
- # Import the recipe
- echo "{@} Building from ${CWD}/$recipe ..."
- . "${CWD}/$recipe"
- # Check for required variables
- if test -z "$program"
- then
- warn "${recipe}: The variable 'program' is not defined."
- exit 5
- fi
- if test -z "$version"
- then
- warn "${recipe}: The variable 'version' is not defined."
- exit 5
- fi
- if test -z "$release"
- then
- warn "${recipe}: The variable 'release' is not defined."
- exit 5
- fi
- # Pre-settings before to start building
- # Increment the release number if the option was given
- if test "$opt_incr_release" = opt_incr_release
- then
- release=$(( release + 1 ))
- fi
- # Allow the dot as definition for 'tardir'
- if test "$tardir" = .
- then
- tardir="$CWD"
- fi
- # Set default values for the following special variables
- pkgname="${pkgname:=$program}"
- pkgversion="${pkgversion:=$version}"
- srcdir="${srcdir:=${program}-$version}"
- destdir="${destdir:=${TMPDIR}/package-$pkgname}"
- # Complete package name adding 'pkgversion-arch+release'
- full_pkgname="${full_pkgname:=$pkgname-${pkgversion}-${arch}+${release}}"
- # If a package is going to be created, the existence of a
- # previous build will be detected and reported. Under normal
- # conditions the recipe is built as long as it is newer than
- # the produced package, if not, we warn to the user about it.
- # Rebuilding the package is possible (through the force ;-)
- if test "$opt_nopkg" != opt_nopkg && \
- { test "$opt_force" != opt_force && \
- test -r "${outdir}/${full_pkgname}.tlz" ; }
- then
- if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
- then
- warn \
- "" \
- "The recipe is more RECENT than the detected package:" \
- "" \
- "$( stat -c "%y %n" "${CWD}/$recipe" )" \
- "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
- "" \
- " This recipe will be processed ..." \
- ""
- elif test -e "${CWD}/post-install" && \
- is_newer "${CWD}/post-install" "${CWD}/$recipe"
- then
- warn \
- "" \
- "The post-install script is more RECENT than the recipe:" \
- "" \
- "$( stat -c "%y %n" "${CWD}/post-install" )" \
- "$( stat -c "%y %n" "${CWD}/$recipe" )" \
- "" \
- " The recipe will be re-processed ..." \
- ""
- touch "${CWD}/$recipe"
- elif test "$opt_ignoreqsts" = opt_ignoreqsts
- then
- warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
- return 0
- else
- warn \
- "" \
- "This recipe ALREADY produced a package:" \
- "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
- "" \
- "The recipe is still OLDER than the produced package:" \
- "$( stat -c "%y %n" "${CWD}/$recipe" )" \
- "" \
- " Probably nothing has changed." \
- ""
- # In non-interactive mode, the user is asked about
- # rebuilding the package. In interactive mode,
- # the user need to pass the option explicitly
- if test ! -t 0
- then
- printf "%b" \
- "Do you want to rebuild this package?\n" \
- "1) Yes, built it\n" \
- "2) No, skipt it [default]\n" \
- "3) Resume, skipping completed recipes\n" \
- "Choose an option number: " > /dev/tty
- IFS= read -r ANSWER < /dev/tty || exit 2;
- case $ANSWER in
- 1*)
- echo "$ANSWER" > /dev/tty
- unset ANSWER
- ;;
- 3*)
- unset ANSWER
- echo "Building UNPROCESSED/MODIFIED recipes ..." > /dev/tty
- echo ""
- opt_ignoreqsts=opt_ignoreqsts
- readonly opt_ignoreqsts
- return 0
- ;;
- *)
- unset ANSWER
- echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
- echo ""
- return 0
- ;;
- esac
- else
- warn "Use the -f option to re-process ${CWD}/$recipe." ""
- return 6;
- fi
- fi
- fi
- # Fetch remote sources
- echo "Fetching remote sources if needed ..."
- if test -n "$fetch"
- then
- for origin in $fetch
- do
- _source="${origin##*/}"
- echo "Looking for \"$_source\" ..."
- echo "Verifying checksum file \"${_source}.sha256\" from '${tardir}'"
- if test -e "${tardir}/${_source}.sha256"
- then
- ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
- chkstatus_or_exit
- continue;
- else
- warn "${_source}.sha256: Checksum file does not exist, yet."
- fi
- # Download source or resume if allowed
- if test ! -e "${tardir}/$_source"
- then
- warn " Can't find $_source in ${tardir};" \
- "attempting to get it from ${origin%/*} ..."
- fi
- case $origin in
- rsync://*)
- (
- cd -- "$tardir" && $rsync $origin || exit $?
- sha256sum $_source > ${_source}.sha256
- ); chkstatus_or_exit 10
- ;;
- *://*)
- (
- cd -- "$tardir" && $netget $origin || exit $?
- sha256sum $_source > ${_source}.sha256
- ); chkstatus_or_exit 10
- ;;
- *)
- warn "${PROGRAM}: Unrecognized protocol for ${origin}."
- exit 4
- esac
- done
- unset origin _source
- else
- warn "The variable 'fetch' is empty."
- fi
- # Prepare special directories for build the source,
- # the destination and the output of the package
- echo "Preparing directories ..."
- if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
- then
- rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
- echo "removed directory: '${TMPDIR}/$srcdir'"
- fi
- if test -d "$destdir"
- then
- rm -rf -- "$destdir" || chkstatus_or_exit
- echo "removed directory: '$destdir'"
- fi
- mkdir -p -- "$destdir" || chkstatus_or_exit
- echo "mkdir: created directory '$destdir'"
- if test ! -d "$outdir"
- then
- mkdir -p -- "$outdir" || chkstatus_or_exit
- echo "mkdir: created directory '$outdir'"
- fi
- echo "Entering to 'TMPDIR': $TMPDIR ..."
- cd -- "$TMPDIR" || chkstatus_or_exit
- # Set trap before to run the build() function in order
- # to catch the return status, exit code 2 if fails
- trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
- # Determine if the debugging indicators of the shell should be
- # retained, assuming that it has been previously passed
- case $- in *x*)
- _xtrace_flag_is_set=xtrace_flag_is_set ;;
- esac
- echo "Running build() ..."
- build
- unset build
- # Turn off possible shell flags coming from the recipe
- set +e
- if test "$_xtrace_flag_is_set" != xtrace_flag_is_set
- then
- set +x
- fi
- # Reset given signals
- trap - EXIT HUP INT QUIT ABRT TERM
- # If 'destdir' is empty, the package won't be created
- if rmdir -- "$destdir" 2> /dev/null
- then
- warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
- opt_nopkg=opt_nopkg
- fi
- # Create (make) the package
- if test "$opt_nopkg" != opt_nopkg
- then
- # Edit the recipe when 'release' is incremented
- if test "$opt_incr_release" = opt_incr_release
- then
- echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
- ed "${CWD}/$recipe" || chkstatus_or_exit
- fi
- mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
- # Include a recipe copy into the package
- cp -p "${CWD}/$recipe" \
- "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
- chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
- # Detect post-install script for inclusion
- if test -f "${CWD}/post-install"
- then
- echo "${CWD}/post-install: Detected."
- cp -p "${CWD}/post-install" \
- "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
- chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
- fi
- # Detect declared package names for later replacement
- if test -n "$replace"
- then
- warn \
- "The following package names has been declared for replacement:" \
- " $replace"
- rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
- for item in $replace
- do
- echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
- done
- unset item
- fi
- # Create meta file for the package information
- echo "Creating meta file ${full_pkgname}.tlz.txt ..."
- do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
- # Make a copy of it for the database
- cp -p "${outdir}/${full_pkgname}.tlz.txt" \
- "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
- # Produce the package
- cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
- fi
- # Back to the current working directory
- cd -- "$CWD" || chkstatus_or_exit
- # Delete 'srcdir' or 'destdir' if -k is not given
- if test "$opt_keep" != opt_keep
- then
- echo "Deleting temporary directories ..."
- srcdir="${srcdir%%/*}" # Directory name without parents.
- if test -r "${TMPDIR}/$srcdir"
- then
- if test -z "$keep_srcdir"
- then
- rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
- echo "removed directory: '${TMPDIR}/${srcdir}'"
- else
- warn "The variable 'keep_srcdir' is set:" \
- "'${TMPDIR}/${srcdir}' will not be deleted."
- fi
- fi
- if test -r "$destdir"
- then
- rm -rf -- "$destdir" || chkstatus_or_exit
- echo "removed directory: '$destdir'"
- fi
- fi
- # Install or update the package if -i or -u was passed
- if test "$opt_nopkg" != opt_nopkg
- then
- if test "$opt_install" = opt_install
- then
- install_mode "${outdir}/${full_pkgname}.tlz"
- elif test "$opt_update" = opt_update
- then
- upgrade_mode "${outdir}/${full_pkgname}.tlz"
- fi
- fi
- echo ""
- echo "All done for ${CWD}/${recipe}."
- echo ""
- }
- create_mode()
- {
- directory=$(dirname -- "$1")
- name=$(basename -- "$1")
- # Perform sanity checks
- is_readable "$directory" || exit 4
- # Check again to find out if it is a valid directory
- test -d "$directory" || {
- warn "Package directory '$name' does not exist."
- exit 4
- }
- test "$directory" = . && {
- warn "Cannot create package on current working directory."
- exit 4
- }
- test "$name" = "${name%.tlz}" && {
- warn \
- "Package format '$name' not supported." \
- "It should be \`name-version-architecture+release.tlz'"
- exit 4
- }
- echo "{#} Creating package $name at $directory ..."
- ( umask 022 ; tar cvf - ./* | lzip -9cvv ) > "${directory}/$name"
- chkstatus_or_exit 3
- echo "Package created \"${directory}/${name}\"."
- echo "${directory}/$name: Creating SHA256 checksum ..."
- ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
- # Remove used variables
- unset directory name
- }
- delete_mode()
- {
- expunge="${packagedir}/$(basename -- $1 .tlz)"
- echo "<<< Deleting package $rootdir${expunge} ..."
- # Complain if the package directory does not exist
- test -d "$rootdir${expunge}" || {
- warn "Package directory '$rootdir${expunge}' does not exist."
- return 4
- }
- # Complain if the package directory cannot be well-read
- is_readable "$rootdir${expunge}" || exit 4
- # Remove package from Graft control
- # Scan for possible conflicts, stop if arise
- if test "$opt_prune" != opt_prune
- then
- echo "Checking for possible conflicts ..."
- if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
- grep ^CONFLICT
- then
- warn "" \
- " A conflict occurred during uninstallation;" \
- "Unless the -p option is given, this package will be PRESERVED."
- return 6;
- fi
- fi
- # Ignore some signals up to completing the deinstallation
- trap "" HUP INT QUIT ABRT TERM
- # Remove objects (files, links or directories) from the target
- # directory that are in conflict with the package directory
- echo "Pruning any conflict ..."
- graft -p -D -u $graft_r -t "$targetdir" "$expunge"
- chkstatus_or_exit 2
- echo "Disabling links ..."
- graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
- chkstatus_or_exit 2
- # Delete package directory
- if test "$opt_keep" != opt_keep
- then
- echo "Deleting package directory, if present ..."
- if test -d "${rootdir}$expunge"
- then
- rm -rf -- "${rootdir}$expunge" || chkstatus_or_exit
- echo "removed directory: '${rootdir}$expunge'"
- fi
- fi
- # Reset given signals
- trap - HUP INT QUIT ABRT TERM
- # Remove used variables
- unset expunge
- }
- install_mode()
- {
- # Complain if the package cannot be well-read
- is_readable "$1" || exit 4
- # Complain if the package does not end in .tlz
- if ! fnmatch '*.tlz' "$1"
- then
- warn "\`${1}' does not end in .tlz"
- return 4
- fi
- # Make preparations to install the package
- echo ">>> Installing package $1 ..."
- # Get the filename
- name=$(basename -- "$1" .tlz)
- echo "Checking tarball integrity ..."
- tar -tf "$1" > /dev/null
- chkstatus_or_exit 3
- # Create package directory using 'name'
- if ! test -d "$rootdir${packagedir}/$name"
- then
- mkdir -p -- "$rootdir${packagedir}/$name" || chkstatus_or_exit
- echo "mkdir: created directory '$rootdir${packagedir}/$name'"
- fi
- # Scan for possible conflicts, stop if arise
- if test "$opt_prune" != opt_prune
- then
- echo "Checking for possible conflicts ..."
- if graft -i -n $graft_r -t "$targetdir" "${packagedir}/$name" 2>&1 | \
- grep ^CONFLICT
- then
- warn "" \
- " A conflict occurred during installation;" \
- "Unless the -p option is given, this package won't be LINKED."
- return 6;
- fi
- fi
- # Ignore some signals up to completing the installation
- trap "" HUP INT QUIT ABRT TERM
- echo "Decompressing $1 ..."
- ( cd -- "$rootdir${packagedir}/$name" && lzip -cd - | tar xpf - ) < "$1"
- chkstatus_or_exit 3
- # Transite package to Graft control
- # Remove objects (files, links or directories) from the target
- # directory that are in conflict with the package directory
- echo "Pruning any conflict ..."
- graft -p -D -u $graft_r -t "$targetdir" "${packagedir}/$name"
- chkstatus_or_exit 2
- echo "Enabling symbolic links ..."
- graft -i -P $graft_v $graft_r -t "$targetdir" "${packagedir}/$name"
- chkstatus_or_exit 2
- # Avoid unnecessary runs coming from the upgrade_mode(),
- # this is when the incoming package is **pre-installed**
- if test "$_isUpgrade" != _isUpgrade.on
- then
- # Show package description
- if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
- then
- grep '^#' "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
- elif test -r "${1}.txt"
- then
- # From external meta file (current directory)
- grep '^#' "${1}.txt"
- else
- warn "Description file not found for '$name'."
- fi
- # Check and run the post-install script if exist
- if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
- then
- echo "Running post-install script for \`${name}' ..."
- (
- # Rely on 'targetdir' if 'rootdir' is empty
- cd -- "${rootdir:-$targetdir}"/ && \
- . "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
- )
- fi
- # Check if there are declared packages for replacement
- if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
- then
- while read -r line
- do
- for replace in "$rootdir${packagedir}/$(pkgbase $line)"-*
- do
- if ! test -e "$replace"
- then
- warn "${replace}: Declared package does not exist. (ignored)"
- continue;
- fi
- replace="${replace##*/}"
- # The search for the package to be replaced cannot
- # be the same to the incoming package, even to the
- # temporary location coming from the upgrade_mode()
- if test "$replace" = "$name" || test "$replace" = "${PRVLOC##*/}"
- then
- continue;
- fi
- warn "WARNING: Replacing package \`${replace}' ..."
- # Since the links belongs to the new package, only
- # those which are not in conflict can be deleted.
- # To complete, we will remove the package directory
- graft -d -D -u $graft_r \
- -t "$targetdir" "$replace" > /dev/null 2>&1
- rm -rf -- "$rootdir${packagedir}/$replace"
- done
- done < "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
- unset line
- fi
- fi
- # Reset given signals
- trap - HUP INT QUIT ABRT TERM
- # Remove used variables
- unset name
- }
- resolve_mode() {
- # Complain if the file cannot be well-read
- is_readable "$1" || exit 4
- # Complain if the file does not end in .order
- if ! fnmatch '*.order' "$1"
- then
- warn "\`${1}' does not end in .order"
- return 4
- fi
- # Get a clean list of the file while prints its content in reverse order,
- # lines containing: colons, comments, parentheses, end of line, and blank
- # lines, are removed. The parentheses are used to insert a reference.
- # The last `awk' in the pipe: removes nonconsecutive lines, duplicate.
- awk \
- '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
- "$1" | awk '!s[$0]++'
- }
- upgrade_mode()
- {
- # Complain if the package is not a regular file
- test -f "$1" || {
- warn "\`${1}' is not a regular file."
- return 4
- }
- # Get the filename
- incoming=$(basename -- "$1" .tlz)
- echo "<>> Upgrading package $incoming ..."
- if test "$opt_force" != opt_force && test -e "${packagedir}/$incoming"
- then
- warn "" \
- " The package to be updated already exist;" \
- "Unless the -f option is given, this package won't be UPGRADED."
- return 6;
- fi
- # Check for packages declared in the black list only for installation
- for item in $blacklist
- do
- case $item in
- ${incoming}*)
- warn \
- "*" \
- "* The incoming package will be installed instead of being updated." \
- "* This may be crucial for the correct functioning of \`${PROGRAM}'." \
- "*"
- opt_prune=opt_prune install_mode "$1"
- return 0;;
- esac
- done
- unset item
- # Prepare the package to install it in a temporary location
- # Set random directory using packagedir as prefix and 'incoming' as suffix
- PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
- # Pre-install the package in the custom 'packagedir'
- save_packagedir="$packagedir"
- packagedir="$PRVLOC"
- echo "Pre-installing package using temporary location ..."
- opt_prune=opt_prune # Turn on prune operation.
- _isUpgrade=_isUpgrade.on install_mode "$1" > /dev/null
- _isUpgrade=_isUpgrade.off
- # Restore variable before looking for old packages
- packagedir=$save_packagedir
- unset save_packagedir
- echo "Looking for installations under the same name ..."
- for long_name in "$rootdir${packagedir}/$(pkgbase $incoming)"*
- do
- found="${long_name##*/}"
- # The search for the package to be deleted
- # cannot be the same to the temporary location
- test "$long_name" = "$PRVLOC" && continue;
- fnmatch "$(pkgbase $found)*" "$incoming" || continue;
- echo "${long_name}: Detected."
- # A package directory is preserved if -k is given
- delete_mode "$found" > /dev/null
- done
- unset long_name found
- # Re-install the package removing the temporary location
- install_mode "$1"
- opt_prune=opt_prune.off # Turn off prune operation.
- echo "Deleting temporary location ..."
- rm -rf -- "$PRVLOC" || chkstatus_or_exit
- echo "removed directory: '$PRVLOC'"
- echo ""
- echo "Successful upgrade to '${incoming}'."
- # Remove remaining variables
- unset incoming PRVLOC
- }
- extract_mode()
- {
- # Perform sanity checks before package extraction
- is_readable "$1" || exit 4
- test -f "$1" || {
- warn "\`${1}' is not a regular file."
- return 4
- }
- # Preparations to extract the package
- name=$(basename -- "$1" .tlz)
- # Set random directory using 'name' as prefix
- PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
- # Trap to remove 'PRVDIR' on disruptions
- trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
- # Create 'PRVDIR' removing access for all but user
- ( umask 077 ; mkdir -- $PRVDIR )
- mkdir -p -m 700 -- $PRVDIR
- echo "Extracting package $name ..."
- ( umask 000 ; cd -- $PRVDIR && lzip -cd - | tar xpvf - ) < "$1"
- if test $? -ne 0
- then
- # Try to remove (empty) 'PRVDIR' on failure
- rmdir -- $PRVDIR
- exit 3;
- fi
- echo "$name has been extracted on $PRVDIR"
- # Reset given signals
- trap - HUP INT ABRT TERM
- # Remove used variables
- unset name PRVDIR
- }
- #### Extra functions used in the modes
- pkgbase()
- {
- string=$(basename -- "$1" .tlz)
- # Match cases to print the package name.
- #
- # We will take into account the four segments removing
- # the last two to print the package (long) name
- case $string in
- *-*-*+*)
- echo "${string%-*-*}"
- ;;
- *)
- echo "$string"
- ;;
- esac
- unset string
- }
- unpack()
- {
- for file in "$@"
- do
- case $file in
- *.tar)
- tar tf "$file" > /dev/null && \
- tar xpf "$file"
- chkstatus_or_exit 3
- ;;
- *.tar.gz | *.tgz | *.tar.Z )
- gzip -cd "$file" | tar tf - > /dev/null && \
- gzip -cd "$file" | tar xpf -
- chkstatus_or_exit 3
- ;;
- *.tar.bz2 | *.tbz2 | *.tbz )
- bzip2 -cd "$file" | tar tf - > /dev/null && \
- bzip2 -cd "$file" | tar xpf -
- chkstatus_or_exit 3
- ;;
- *.tar.lz | *.tlz )
- lzip -cd "$file" | tar tf - > /dev/null && \
- lzip -cd "$file" | tar xpf -
- chkstatus_or_exit 3
- ;;
- *.tar.xz | *.txz )
- xz -cd "$file" | tar tf - > /dev/null && \
- xz -cd "$file" | tar xpf -
- chkstatus_or_exit 3
- ;;
- *.zip | *.ZIP )
- unzip -t "$file" > /dev/null && \
- unzip "$file" > /dev/null
- chkstatus_or_exit 3
- ;;
- *.gz)
- gzip -t "$file" && \
- gzip -cd "$file" > "$(basename -- $file .gz)"
- chkstatus_or_exit 3
- ;;
- *.Z)
- gzip -t "$file" && \
- gzip -cd "$file" > "$(basename -- $file .Z)"
- chkstatus_or_exit 3
- ;;
- *.bz2)
- bzip2 -t "$file" && \
- bzip2 -cd "$file" > "$(basename -- $file .bz2)"
- chkstatus_or_exit 3
- ;;
- *.lz)
- lzip -t "$file" && \
- lzip -cd "$file" > "$(basename -- $file .lz)"
- chkstatus_or_exit 3
- ;;
- *.xz)
- xz -t "$file" && \
- xz -cd "$file" > "$(basename -- $file .xz)"
- chkstatus_or_exit 3
- ;;
- *)
- warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
- exit 1
- esac
- done
- unset file
- }
- do_meta()
- {
- # Extract information from the recipe to create the meta file.
- #
- # The package description is pre-formatted in 78 columns,
- # the '#' character and a space is added as prefix to conform
- # 80 columns in total
- cat << EOF
- $(echo "$description" | fold -w 78 | awk '$0="# " $0')
- QICFLAGS="$QICFLAGS"
- QICXXFLAGS="$QICXXFLAGS"
- QILDFLAGS="$QILDFLAGS"
- program=$program
- version=$version
- release=$release
- blurb="$(echo "$description" | sed -e '/^$/d;2q')"
- homepage="$homepage"
- license="$license"
- fetch="$fetch"
- replace="$replace"
- EOF
- }
- #### Default values
- PROGRAM="${0##*/}"
- packagedir=@PACKAGEDIR@
- targetdir=@TARGETDIR@
- blacklist="perl graft musl glibc"
- RC=RC
- RCFILE=@SYSCONFDIR@/qirc
- opt_install=opt_install.off
- opt_update=opt_update.off
- opt_force=opt_force.off
- opt_keep=opt_keep.off
- opt_incr_release=opt_incr_release.off
- opt_ignoreqsts=opt_ignoreqsts.off
- opt_nopkg=opt_nopkg.off
- opt_prune=opt_prune.off
- rootdir=""
- jobs=1
- mode=""
- verbose=0
- graft_v=-v
- graft_r=""
- _isUpgrade=_isUpgrade.off
- TMPDIR="${TMPDIR:=/usr/src/qi/build}"
- QICFLAGS="${QICFLAGS:=-g0 -Os}"
- QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
- QILDFLAGS="${QILDFLAGS:=-s}"
- worktree=/usr/src/qi
- tardir=${worktree}/sources
- outdir=/var/cache/qi/packages
- netget="wget -c -w1 -t3 --no-check-certificate"
- rsync="rsync -v -a -L -z -i --progress"
- configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
- infodir=@INFODIR@
- mandir=@MANDIR@
- docdir=@DOCDIR@
- # Store default locations
- QI_TARGETDIR=$targetdir
- QI_PACKAGEDIR=$packagedir
- QI_WORKTREE=$worktree
- QI_TARDIR=$tardir
- QI_OUTDIR=$outdir
- #### Parse options
- while getopts :bcdiouxLNP:t:fkvO:W:Z:a:j:13npr:hV name
- do
- case $name in
- b)
- if test -z "$mode"
- then
- readconfig
- mode=build_mode
- fi
- ;;
- c)
- mode=create_mode
- ;;
- d)
- readconfig
- mode=delete_mode
- ;;
- i)
- if test -z "$mode"
- then
- readconfig
- mode=install_mode
- fi
- if test "$mode" = build_mode
- then
- opt_install=opt_install
- fi
- ;;
- o)
- mode=resolve_mode
- ;;
- u)
- if test -z "$mode"
- then
- readconfig
- mode=upgrade_mode
- fi
- if test "$mode" = build_mode
- then
- opt_update=opt_update
- fi
- ;;
- x)
- mode=extract_mode
- ;;
- L)
- printf "%s\n" \
- "QI_TARGETDIR=$QI_TARGETDIR" \
- "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
- "QI_WORKTREE=$QI_WORKTREE" \
- "QI_TARDIR=$QI_TARDIR" \
- "QI_OUTDIR=$QI_OUTDIR"
- exit 0
- ;;
- N)
- RC=RC.off
- ;;
- P)
- packagedir="$OPTARG"
- ;;
- t)
- targetdir="$OPTARG"
- ;;
- f)
- opt_force=opt_force
- ;;
- k)
- opt_keep=opt_keep
- ;;
- v)
- verbose=$(( verbose + 1 ))
- ;;
- O)
- outdir="$OPTARG"
- ;;
- W)
- worktree="$OPTARG"
- ;;
- Z)
- tardir="$OPTARG"
- ;;
- a)
- arch="$OPTARG"
- ;;
- j)
- jobs="$OPTARG"
- ;;
- 1)
- opt_incr_release=opt_incr_release
- ;;
- 3)
- opt_ignoreqsts=opt_ignoreqsts
- ;;
- n)
- opt_nopkg=opt_nopkg
- ;;
- p)
- opt_prune=opt_prune
- ;;
- r)
- rootdir="$OPTARG"
- ;;
- h)
- usage
- exit 0
- ;;
- V)
- version
- exit 0
- ;;
- :)
- warn "Option '-${OPTARG}' requires an argument"
- usage
- exit 1
- ;;
- \?)
- warn "Illegal option -- '-${OPTARG}'"
- usage
- exit 1
- ;;
- esac
- done
- shift $(( OPTIND - 1 ))
- if test $# -eq 0
- then
- usage
- exit 1
- fi
- # Program sanity check
- for need in awk basename bzip2 chmod cp dirname find fold graft grep \
- lzip mkdir mktemp rm rmdir sed sha256sum stat tar
- do
- if ! type $need 1> /dev/null 2> /dev/null
- then
- warn "${PROGRAM}: cannot operate without ${need}(1): Check your PATH"
- exit 2
- fi
- done
- unset need
- # Determine verbosity level/flag
- if test "$verbose" -gt 1
- then
- graft_v=-V
- fi
- # Read standard input if FILE is -, or when FILE
- # is not connected to a terminal.
- if test "$1" = - || test ! -t 0
- then
- # Unset positional parameters setting $# to zero
- set --
- # Assign remaining arguments to the positional parameters
- while read -r input
- do
- set -- "$@" "$input"
- done
- fi
- # We need at least one operating mode
- if test -z "$mode"
- then
- usage
- exit 4
- fi
- umask 022; # Remove write permission for group and other.
- # Validate 'rootdir' directory
- if test -n "$rootdir"
- then
- if test -d "$rootdir" && test "$rootdir" != /
- then
- # Remove slash from the end
- rootdir="${rootdir%/}"
- # A workaround for graft-2.13+. The specified directory is
- # relative to the log file, we prepend it inside the rootdir
- eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
- mkdir -p -- "$rootdir$(dirname -- $GRAFT_LOGFILE)" || chkstatus_or_exit
- # Compose 'rootdir' and log file option to be used with graft(1)
- graft_r="-r $rootdir -l $GRAFT_LOGFILE"
- # Unset variables coming from eval
- unset GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
- else
- warn "${PROGRAM}: \`${rootdir}' is not a qualified root directory"
- exit 4
- fi
- export rootdir
- fi
- # Ensure 'TMPDIR' creation to prefix temporary files
- if test ! -d "$TMPDIR"
- then
- mkdir -p -- "$TMPDIR" || chkstatus_or_exit
- fi
- readonly TMPDIR
- # Process each package or recipe provided on the command-line
- for package in "$@"
- do
- $mode $package
- done
|