From 512436524cd3e70b9394d304bc9a43c6858c3695 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 17 Jan 2013 21:14:02 -0500 Subject: Squashed 'src/devtools/' content from commit 2cda43f git-subtree-dir: src/devtools git-subtree-split: 2cda43f4fa3d51f3cbcb05950186896eb9c01314 --- .gitignore | 17 +++ Makefile | 55 +++++++++ README | 12 ++ bash_completion.in | 23 ++++ checkpkg.in | 83 ++++++++++++++ find-libdeps.in | 87 ++++++++++++++ finddeps.in | 39 +++++++ gitconfig | 17 +++ lddd.in | 48 ++++++++ lib/common.sh | 186 ++++++++++++++++++++++++++++++ makechrootpkg.in | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mkarchroot.in | 302 ++++++++++++++++++++++++++++++++++++++++++++++++ zsh_completion.in | 34 ++++++ 13 files changed, 1231 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README create mode 100644 bash_completion.in create mode 100644 checkpkg.in create mode 100644 find-libdeps.in create mode 100644 finddeps.in create mode 100644 gitconfig create mode 100644 lddd.in create mode 100644 lib/common.sh create mode 100644 makechrootpkg.in create mode 100644 mkarchroot.in create mode 100644 zsh_completion.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5e29ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*~ +devtools-*.tar.gz* +archbuild +archco +archrelease +archrm +bash_completion +checkpkg +commitpkg +finddeps +lddd +makechrootpkg +mkarchroot +rebuildpkgs +zsh_completion +find-libdeps +crossrepomove diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8b94f80 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +V=20121128.6 + +PREFIX = /usr/local +pkgdatadir=$(PREFIX)/share/devtools + +BINPROGS = \ + checkpkg \ + find-libdeps \ + finddeps \ + lddd + +SBINPROGS = \ + mkarchroot + +all: $(BINPROGS) $(SBINPROGS) bash_completion zsh_completion + +edit = sed -e "s|@pkgdatadir[@]|$(pkgdatadir)|g" + +%: %.in Makefile + @echo "GEN $@" + @$(RM) "$@" + @m4 -P $@.in | $(edit) >$@ + @chmod a-w "$@" + @chmod +x "$@" + +clean: + rm -f $(BINPROGS) $(SBINPROGS) bash_completion zsh_completion + +install: all + install -dm0755 $(DESTDIR)$(PREFIX)/bin + install -dm0755 $(DESTDIR)$(PREFIX)/sbin + install -dm0755 $(DESTDIR)$(pkgdatadir) + + install -m0755 ${BINPROGS} $(DESTDIR)$(PREFIX)/bin +# install -m0755 ${SBINPROGS} $(DESTDIR)$(PREFIX)/sbin + install -m0755 mkarchroot $(DESTDIR)$(PREFIX)/sbin/archroot + + ln -sf find-libdeps $(DESTDIR)$(PREFIX)/bin/find-libprovides + + install -m0644 lib/common.sh $(DESTDIR)$(pkgdatadir)/common.sh + install -m0644 makechrootpkg.in $(DESTDIR)$(pkgdatadir)/makechrootpkg.sh + install -Dm0644 bash_completion $(DESTDIR)$(PREFIX)/share/bash-completion/completions/devtools + install -Dm0644 zsh_completion $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_devtools + +uninstall: + for f in ${BINPROGS} ; do rm -f $(DESTDIR)$(PREFIX)/bin/$$f; done +# for f in ${SBINPROGS} ; do rm -f $(DESTDIR)$(PREFIX)/sbin/$$f; done + rm -f $(DESTDIR)$(PREFIX)/sbin/archroot + + rm -f $(DESTDIR)$(PREFIX)/bin/find-libprovides + + rm -f $(DESTDIR)$(PREFIX)/share/bash-completion/completions/devtools + rm -f $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_devtools + +.PHONY: all clean install uninstall diff --git a/README b/README new file mode 100644 index 0000000..e10394f --- /dev/null +++ b/README @@ -0,0 +1,12 @@ +This is a minimal fork of Arch's `devtools'. + +It is a fork in that bugs are fixed and features added. + This happens on the "complete" branch. Most development should happen here, + and it should be able to be merged back into devtools. +It is minimal in that it doesn't include most of what is in devtools. + This happens on the "master" branch. + +A sample .git/config file is included to make it easy to merge from devtools. + +Tags in the format "%YYYY%MM%DD" are devtools. +Tags in the format "v%YYYY%MM%DD" are chroottools. diff --git a/bash_completion.in b/bash_completion.in new file mode 100644 index 0000000..5e4fe66 --- /dev/null +++ b/bash_completion.in @@ -0,0 +1,23 @@ +#!/bin/bash + +_mkarchroot() { + local cur + COMPREPLY=() + _get_comp_words_by_ref cur + + case $cur in + -*) + COMPREPLY=( $( compgen -W '-C -M -c -f -h -n -r -u' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + ;; + esac + + true +} && +complete -F _mkarchroot archroot + + +# ex:et ts=2 sw=2 ft=sh diff --git a/checkpkg.in b/checkpkg.in new file mode 100644 index 0000000..a761df7 --- /dev/null +++ b/checkpkg.in @@ -0,0 +1,83 @@ +#!/bin/bash + +source @pkgdatadir@/common.sh + +# Source makepkg.conf; fail if it is not found +if [[ -r '/etc/makepkg.conf' ]]; then + source '/etc/makepkg.conf' +else + die '/etc/makepkg.conf not found!' +fi + +# Source user-specific makepkg.conf overrides +if [[ -r ~/.makepkg.conf ]]; then + source ~/.makepkg.conf +fi + +if [[ ! -f PKGBUILD ]]; then + die 'This must be run in the directory of a built package.' +fi + +. PKGBUILD +if [[ $arch == 'any' ]]; then + CARCH='any' +fi + +STARTDIR=$(pwd) +TEMPDIR=$(mktemp -d --tmpdir checkpkg-script.XXXX) +cd "$TEMPDIR" + +for _pkgname in "${pkgname[@]}"; do + pkgfile=${_pkgname}-$(get_full_version $_pkgname)-${CARCH}${PKGEXT} + + if [[ -f "$STARTDIR/$pkgfile" ]]; then + ln -s "$STARTDIR/$pkgfile" "$pkgfile" + elif [[ -f "$PKGDEST/$pkgfile" ]]; then + ln -s "$PKGDEST/$pkgfile" "$pkgfile" + else + die "File \"$pkgfile\" doesn't exist" + fi + + pkgurl=$(pacman -Spdd --print-format '%l' --noconfirm "$_pkgname") + + if [[ $? -ne 0 ]]; then + die "Couldn't download previous package for $_pkgname." + fi + + oldpkg=${pkgurl##*://*/} + + if [[ ${oldpkg##*/} = ${pkgfile##*/} ]]; then + die "The built package ($_pkgname) is the one in the repo right now!" + fi + + if [[ ! -f $oldpkg ]]; then + if [[ $pkgurl = file://* ]]; then + ln -s "${pkgurl#file://}" "${pkgurl##file://*/}" + elif [[ -f "$PKGDEST/$oldpkg" ]]; then + ln -s "$PKGDEST/$oldpkg" "$oldpkg" + elif [[ -f "$STARTDIR/$oldpkg" ]]; then + ln -s "$STARTDIR/$oldpkg" "$oldpkg" + else + curl -fsLC - --retry 3 --retry-delay 3 -o "$oldpkg" "$pkgurl" + fi + fi + + bsdtar tf "$oldpkg" | sort > "filelist-$_pkgname-old" + bsdtar tf "$pkgfile" | sort > "filelist-$_pkgname" + + sdiff -s "filelist-$_pkgname-old" "filelist-$_pkgname" + + if diff "filelist-$_pkgname-old" "filelist-$_pkgname" | grep '\.so' > /dev/null 2>&1; then + mkdir -p pkg + cd pkg + bsdtar xf ../"$pkgfile" > /dev/null + diff "../filelist-$_pkgname-old" "../filelist-$_pkgname" | awk '/>.*\.so/{$1 = ""; print $0}' | while read i; do + echo "${i}: " "$(objdump -p "$i" | grep SONAME)" + done + cd .. + else + msg "No soname differences for $_pkgname." + fi +done + +msg "Files saved to $TEMPDIR" diff --git a/find-libdeps.in b/find-libdeps.in new file mode 100644 index 0000000..7618850 --- /dev/null +++ b/find-libdeps.in @@ -0,0 +1,87 @@ +#!/bin/bash + +source @pkgdatadir@/common.sh + +set -e +shopt -s extglob + +IGNORE_INTERNAL=0 + +if [[ $1 = "--ignore-internal" ]]; then + IGNORE_INTERNAL=1 + shift +fi + +script_mode=${0##*/find-lib} + +case $script_mode in + deps|provides) true;; + *) die "Unknown mode $script_mode" ;; +esac + +if [[ -z $1 ]]; then + echo "${0##*/} [options] " + echo "Options:" + echo " --ignore-internal ignore internal libraries" + exit 1 +fi + +if [[ -d $1 ]]; then + pushd $1 >/dev/null +else + setup_workdir + + case ${script_mode} in + deps) bsdtar -C $WORKDIR -xf "$1";; + provides) bsdtar -C $WORKDIR -xf "$1" --include="*.so*";; + esac + + pushd $WORKDIR >/dev/null +fi + +process_sofile() { + # extract the library name: libfoo.so + soname="${sofile%.so?(+(.+([0-9])))}".so + # extract the major version: 1 + soversion="${sofile##*\.so\.}" + if [[ "$soversion" = "$sofile" ]] && (($IGNORE_INTERNAL)); then + continue + fi + if ! in_array "${soname}=${soversion}-${soarch}" ${soobjects[@]}; then + # libfoo.so=1-64 + echo "${soname}=${soversion}-${soarch}" + soobjects=(${soobjects[@]} "${soname}=${soversion}-${soarch}") + fi +} + +case $script_mode in + deps) find_args="-perm -u+x";; + provides) find_args="-name *.so*";; +esac + +find . -type f $find_args | while read filename; do + if [[ $script_mode = "provides" ]]; then + # ignore if we don't have a shared object + if ! LC_ALL=C readelf -h "$filename" 2>/dev/null | grep -q '.*Type:.*DYN (Shared object file).*'; then + continue + fi + fi + + # get architecture of the file; if soarch is empty it's not an ELF binary + soarch=$(LC_ALL=C readelf -h "$filename" 2>/dev/null | sed -n 's/.*Class.*ELF\(32\|64\)/\1/p') + [[ -n $soarch ]] || continue + + if [[ $script_mode = "provides" ]]; then + # get the string binaries link to: libfoo.so.1.2 -> libfoo.so.1 + sofile=$(LC_ALL=C readelf -d "$filename" 2>/dev/null | sed -n 's/.*Library soname: \[\(.*\)\].*/\1/p') + [[ -z $sofile ]] && sofile="${filename##*/}" + process_sofile + elif [[ $script_mode = "deps" ]]; then + # process all libraries needed by the binary + for sofile in $(LC_ALL=C readelf -d "$filename" 2>/dev/null | sed -nr 's/.*Shared library: \[(.*)\].*/\1/p'); do + process_sofile + done + fi +done + +popd >/dev/null diff --git a/finddeps.in b/finddeps.in new file mode 100644 index 0000000..656fe5a --- /dev/null +++ b/finddeps.in @@ -0,0 +1,39 @@ +#!/bin/bash +# +# finddeps - find packages that depend on a given depname +# + +source @pkgdatadir@/common.sh + +match=$1 + +if [[ -z $match ]]; then + echo 'Usage: finddeps ' + echo '' + echo 'Find packages that depend on a given depname.' + echo 'Run this script from the top-level directory of your ABS tree.' + echo '' + exit 1 +fi + +find . -type d | while read d; do + if [[ -f "$d/PKGBUILD" ]]; then + unset pkgname depends makedepends optdepends + . "$d/PKGBUILD" + for dep in "${depends[@]}"; do + # lose the version comparator, if any + depname=${dep%%[<>=]*} + [[ $depname = $match ]] && echo "$d (depends)" + done + for dep in "${makedepends[@]}"; do + # lose the version comparator, if any + depname=${dep%%[<>=]*} + [[ $depname = $match ]] && echo "$d (makedepends)" + done + for dep in "${optdepends[@]/:*}"; do + # lose the version comaparator, if any + depname=${dep%%[<>=]*} + [[ $depname = $match ]] && echo "$d (optdepends)" + done + fi +done diff --git a/gitconfig b/gitconfig new file mode 100644 index 0000000..890a5d0 --- /dev/null +++ b/gitconfig @@ -0,0 +1,17 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[remote "origin"] + url = ssh://git@parabolagnulinux.org:1863/srv/git/packages/chroottools.git + fetch = +refs/heads/*:refs/remotes/origin/* +[remote "devtools"] + url = git://projects.archlinux.org/devtools.git + fetch = +refs/heads/*:refs/remotes/devtools/* +[branch "master"] + remote = origin + merge = refs/heads/master +[branch "complete"] + remote = devtools + merge = refs/heads/master diff --git a/lddd.in b/lddd.in new file mode 100644 index 0000000..4040ce6 --- /dev/null +++ b/lddd.in @@ -0,0 +1,48 @@ +#!/bin/bash +# +# lddd - find broken library links on your machine +# + +source @pkgdatadir@/common.sh + +ifs=$IFS +IFS="${IFS}:" + +libdirs="/lib /usr/lib /usr/local/lib $(cat /etc/ld.so.conf.d/*)" +extras= + +TEMPDIR=$(mktemp -d --tmpdir lddd-script.XXXX) + +msg 'Go out and drink some tea, this will take a while :) ...' +# Check ELF binaries in the PATH and specified dir trees. +for tree in $PATH $libdirs $extras; do + msg2 "DIR $tree" + + # Get list of files in tree. + files=$(find $tree -type f ! -name '*.a' ! -name '*.la' ! -name '*.py*' ! -name '*.txt' ! -name '*.h' ! -name '*.ttf' ! \ + -name '*.rb' ! -name '*.ko' ! -name '*.pc' ! -name '*.enc' ! -name '*.cf' ! -name '*.def' ! -name '*.rules' ! -name \ + '*.cmi' ! -name '*.mli' ! -name '*.ml' ! -name '*.cma' ! -name '*.cmx' ! -name '*.cmxa' ! -name '*.pod' ! -name '*.pm' \ + ! -name '*.pl' ! -name '*.al' ! -name '*.tcl' ! -name '*.bs' ! -name '*.o' ! -name '*.png' ! -name '*.gif' ! -name '*.cmo' \ + ! -name '*.cgi' ! -name '*.defs' ! -name '*.conf' ! -name '*_LOCALE' ! -name 'Compose' ! -name '*_OBJS' ! -name '*.msg' ! \ + -name '*.mcopclass' ! -name '*.mcoptype') + IFS=$ifs + for i in $files; do + if (( $(file $i | grep -c 'ELF') != 0 )); then + # Is an ELF binary. + if (( $(ldd $i 2>/dev/null | grep -c 'not found') != 0 )); then + # Missing lib. + echo "$i:" >> $TEMPDIR/raw.txt + ldd $i 2>/dev/null | grep 'not found' >> $TEMPDIR/raw.txt + fi + fi + done +done +grep '^/' $TEMPDIR/raw.txt | sed -e 's/://g' >> $TEMPDIR/affected-files.txt +# invoke pacman +for i in $(cat $TEMPDIR/affected-files.txt); do + pacman -Qo $i | awk '{print $4,$5}' >> $TEMPDIR/pacman.txt +done +# clean list +sort -u $TEMPDIR/pacman.txt >> $TEMPDIR/possible-rebuilds.txt + +msg "Files saved to $TEMPDIR" diff --git a/lib/common.sh b/lib/common.sh new file mode 100644 index 0000000..d6fbe7c --- /dev/null +++ b/lib/common.sh @@ -0,0 +1,186 @@ +# Avoid any encoding problems +export LANG=C + +# check if messages are to be printed using color +unset ALL_OFF BOLD BLUE GREEN RED YELLOW +if [[ -t 2 ]]; then + # prefer terminal safe colored and bold text when tput is supported + if tput setaf 0 &>/dev/null; then + ALL_OFF="$(tput sgr0)" + BOLD="$(tput bold)" + BLUE="${BOLD}$(tput setaf 4)" + GREEN="${BOLD}$(tput setaf 2)" + RED="${BOLD}$(tput setaf 1)" + YELLOW="${BOLD}$(tput setaf 3)" + else + ALL_OFF="\e[1;0m" + BOLD="\e[1;1m" + BLUE="${BOLD}\e[1;34m" + GREEN="${BOLD}\e[1;32m" + RED="${BOLD}\e[1;31m" + YELLOW="${BOLD}\e[1;33m" + fi +fi +readonly ALL_OFF BOLD BLUE GREEN RED YELLOW + +plain() { + local mesg=$1; shift + printf "${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +msg() { + local mesg=$1; shift + printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +msg2() { + local mesg=$1; shift + printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +warning() { + local mesg=$1; shift + printf "${YELLOW}==> WARNING:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +error() { + local mesg=$1; shift + printf "${RED}==> ERROR:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 +} + +stat_busy() { + local mesg=$1; shift + printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}...${ALL_OFF}" >&2 +} + +stat_done() { + printf "${BOLD}done${ALL_OFF}\n" >&2 +} + +_setup_workdir=false +setup_workdir() { + [[ -z $WORKDIR ]] && WORKDIR=$(mktemp -d --tmpdir "${0##*/}.XXXXXXXXXX") + _setup_workdir=true + trap 'trap_abort' INT QUIT TERM HUP + trap 'trap_exit' EXIT +} + +cleanup() { + if [[ -n $WORKDIR ]] && $_setup_workdir; then + rm -rf "$WORKDIR" + fi + [[ -n $1 ]] && exit $1 +} + +abort() { + msg 'Aborting...' + cleanup 0 +} + +trap_abort() { + trap - EXIT INT QUIT TERM HUP + abort +} + +trap_exit() { + trap - EXIT INT QUIT TERM HUP + cleanup +} + +die() { + error "$*" + cleanup 1 +} + +## +# usage : in_array( $needle, $haystack ) +# return : 0 - found +# 1 - not found +## +in_array() { + local needle=$1; shift + local item + for item in "$@"; do + [[ $item = $needle ]] && return 0 # Found + done + return 1 # Not Found +} + +## +# usage : lock_open_write( $fd, $path, $wait_message ) +## +lock_open_write() { + local fd=$1 + local path=$2 + local msg=$3 + + # Only reopen the FD if it wasn't handed to us + if [[ $(readlink -f /dev/fd/$fd) != "${path}.lock" ]]; then + mkdir -p "${path%/*}" + eval "exec $fd>${path}.lock" + fi + + if ! flock -n $fd; then + stat_busy "$msg" + flock $fd + stat_done + fi +} + +## +# usage : lock_open_read( $fd, $path, $wait_message ) +## +lock_open_read() { + local fd=$1 + local path=$2 + local msg=$3 + + # Only reopen the FD if it wasn't handed to us + if [[ $(readlink -f /dev/fd/$fd) != "${path}.lock" ]]; then + mkdir -p "${path%/*}" + eval "exec $fd>${path}.lock" + fi + + if ! flock -sn $fd; then + stat_busy "$msg" + flock -s $fd + stat_done + fi +} + + +## +# usage : lock_close( $fd ) +## +lock_close() { + local fd=$1 + eval "exec $fd>&-" +} + +## +# usage : get_full_version( [$pkgname] ) +# return : full version spec, including epoch (if necessary), pkgver, pkgrel +## +get_full_version() { + # set defaults if they weren't specified in buildfile + pkgbase=${pkgbase:-${pkgname[0]}} + epoch=${epoch:-0} + if [[ -z $1 ]]; then + if [[ $epoch ]] && (( ! $epoch )); then + echo $pkgver-$pkgrel + else + echo $epoch:$pkgver-$pkgrel + fi + else + for i in pkgver pkgrel epoch; do + local indirect="${i}_override" + eval $(declare -f package_$1 | sed -n "s/\(^[[:space:]]*$i=\)/${i}_override=/p") + [[ -z ${!indirect} ]] && eval ${indirect}=\"${!i}\" + done + if (( ! $epoch_override )); then + echo $pkgver_override-$pkgrel_override + else + echo $epoch_override:$pkgver_override-$pkgrel_override + fi + fi +} diff --git a/makechrootpkg.in b/makechrootpkg.in new file mode 100644 index 0000000..9e5b04f --- /dev/null +++ b/makechrootpkg.in @@ -0,0 +1,328 @@ +#!/bin/bash +# Copyright 2011-2012 The Arch Linux Development Team +# Copyright 2012 Luke Shumaker +# +# 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; version 2 of the License. +# +# 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. + +# Because of how we pull changes from devtools.git, some of the function +# bodies are not indented. I appologize for my sins against the reader. + +# Any function beginning with "_makechrootpkg_" is basically a multiline +# comment to minimize the diff. These functions may use different variable +# names. + +# Otherwise, a function may be affected by the variables: +# makepkg.conf +# - PKGDEST +# - SRCDEST +# chroot.conf (and derived) +# - CHROOT +# - CHROOTCOPY +# - rootdir # = $CHROOTDIR/$CHROOT/root +# - copydir # = $CHROOTDIR/$CHROOT/$CHROOTCOPY +# environment +# - LIBREUSER # conf.sh +# - LIBREHOME # conf.sh +# - INCHROOT # libremakepkg +# invocation +# - repack + +[[ -n ${repack:-} ]] || repack=false + +_makechrootpkg_init() { +m4_include(lib/common.sh) + +shopt -s nullglob + +makepkg_args='-s --noconfirm -L' +repack=false +update_first=false +clean_first=false +install_pkg= +add_to_db=false +run_namcap=false +chrootdir= +passeddir= + +default_copy=$USER +[[ -n $SUDO_USER ]] && default_copy=$SUDO_USER +[[ -z $default_copy || $default_copy = root ]] && default_copy=copy +src_owner=${SUDO_USER:-$USER} +} + +_makechrootpkg_usage() { + echo "Usage: ${0##*/} [options] -r [--] [makepkg args]" + echo ' Run this script in a PKGBUILD dir to build a package inside a' + echo ' clean chroot. All unrecognized arguments passed to this script' + echo ' will be passed to makepkg.' + echo '' + echo ' The chroot dir consists of the following directories:' + echo ' /{root, copy} but only "root" is required' + echo ' by default. The working copy will be created as needed' + echo '' + echo 'The chroot "root" directory must be created via the following' + echo 'command:' + echo ' mkarchroot /root base base-devel sudo' + echo '' + echo "Default makepkg args: $makepkg_args" + echo '' + echo 'Flags:' + echo '-h This help' + echo '-c Clean the chroot before building' + echo '-u Update the working copy of the chroot before building' + echo ' This is useful for rebuilds without dirtying the pristine' + echo ' chroot' + echo '-d Add the package to a local db at /repo after building' + echo '-r The chroot dir to use' + echo '-I Install a package into the working copy of the chroot' + echo '-l The directory to use as the working copy of the chroot' + echo ' Useful for maintaining multiple copies.' + echo " Default: $default_copy" + echo '-n Run namcap on the package' + exit 1 +} + +_makechrootpkg_parse_options_init() { +while getopts 'hcudr:I:l:n' arg; do + case "$arg" in + h) usage ;; + c) clean_first=true ;; + u) update_first=true ;; + d) add_to_db=true ;; + r) passeddir="$OPTARG" ;; + I) install_pkg="$OPTARG" ;; + l) copy="$OPTARG" ;; + n) run_namcap=true; makepkg_args="$makepkg_args -i" ;; + *) makepkg_args="$makepkg_args -$arg $OPTARG" ;; + esac +done + +# Canonicalize chrootdir, getting rid of trailing / +chrootdir=$(readlink -e "$passeddir") + +if [[ ${copy:0:1} = / ]]; then + copydir=$copy +else + [[ -z $copy ]] && copy=$default_copy + copydir="$chrootdir/$copy" +fi + +# Pass all arguments after -- right to makepkg +makepkg_args="$makepkg_args ${*:$OPTIND}" + +# See if -R was passed to makepkg +for arg in ${*:$OPTIND}; do + if [[ $arg = -R ]]; then + repack=true + break + fi +done + +if (( EUID )); then + die 'This script must be run as root.' +fi + +if [[ ! -f PKGBUILD && -z $install_pkg ]]; then + die 'This must be run in a directory containing a PKGBUILD.' +fi + +if [[ ! -d $chrootdir ]]; then + die "No chroot dir defined, or invalid path '$passeddir'" +fi + +if [[ ! -d $chrootdir/root ]]; then + die "Missing chroot dir root directory. Try using: mkarchroot $chrootdir/root base base-devel sudo" +fi + +umask 0022 + +# Lock the chroot we want to use. We'll keep this lock until we exit. +# Note this is the same FD number as in mkarchroot +lock_open_write 9 "$copydir.lock" "Locking chroot copy '$copy'" +} + +chroot_sync() { + if [[ $CHROOTCOPY = root ]]; then + error "Cannot sync the root copy with itself" + exit 1 + fi + # Get a read lock on the root chroot to make + # sure we don't clone a half-updated chroot + lock_open_read 8 "$rootdir" \ + "Waiting for existing lock on \`$rootdir' to be released" + + stat_busy 'Creating clean working copy' + local use_rsync=false + if type -P btrfs >/dev/null; then + [[ -d $copydir ]] && btrfs subvolume delete "$copydir" &>/dev/null + btrfs subvolume snapshot "$rootdir" "$copydir" &>/dev/null || + use_rsync=true + else + use_rsync=true + fi + + if $use_rsync; then + mkdir -p "$copydir" + rsync -a --delete -q -W -x "$rootdir/" "$copydir" + fi + stat_done + + # Drop the read lock again + lock_close 8 +} + +_makechrootpkg_install_pkg() { + pkgname="${install_pkg##*/}" + cp "$install_pkg" "$copydir/$pkgname" + + mkarchroot -r "pacman -U /$pkgname --noconfirm" "$copydir" + ret=$? + + rm "$copydir/$pkgname" + + # Exit early, we've done all we need to + exit $ret +} + +chroot_init() { +# make sure the chroot exists +librechroot -n "$CHROOT" -l "$CHROOTCOPY" -m + +mkdir -p "$copydir/build" +mkdir -p "$copydir/pkgdest" +mkdir -p "$copydir/srcdest" + +# Remove anything in there UNLESS -R (repack) was passed to makepkg +$repack || rm -rf "$copydir"/build/* + +if [[ -r "$LIBREHOME/.gnupg/pubring.gpg" ]]; then + install -D "$LIBREHOME/.gnupg/pubring.gpg" "$copydir/build/.gnupg/pubring.gpg" +fi +rm -f "$copydir/build/.makepkg.conf" + +MAKEPKG_CONF="$copydir/etc/makepkg.conf" set_conf_makepkg PKGDEST /pkgdest +MAKEPKG_CONF="$copydir/etc/makepkg.conf" set_conf_makepkg SRCDEST /srcdest + +if grep -q '^\[repo\]' "$copydir/etc/pacman.conf"; then + cat >> "$copydir/etc/makepkg.conf" < "$copydir/etc/sudoers.d/nobody-pacman" <"$copydir/chrootexec" </dev/null + cp "$pkgfile" . + repo-add repo.db.tar.gz "${pkgfile##*/}" + popd >/dev/null + fi + done +} +_chroot_copy_out_pkgs() { + for pkgfile in "$copydir"/pkgdest/*.pkg.tar*; do + chown "$LIBREUSER" "$pkgfile" + mv "$pkgfile" "$PKGDEST" + if [[ $PKGDEST != . ]]; then + ln -s "$PKGDEST/${pkgfile##*/}" . + fi + done +} + +_chroot_copy_out_logs() { + for l in "$copydir"/build/*.log; do + chown "$LIBREUSER" "$l" + [[ -f $l ]] && mv "$l" . + done +} + +_chroot_copy_out_srcs() { +for f in "$copydir"/srcdest/*; do + chown "$LIBREUSER" "$f" + mv "$f" "$SRCDEST" +done +} + +chroot_copy_out() { + _chroot_copy_out_pkgs + _chroot_copy_out_logs + _chroot_copy_out_srcs +} diff --git a/mkarchroot.in b/mkarchroot.in new file mode 100644 index 0000000..96f4399 --- /dev/null +++ b/mkarchroot.in @@ -0,0 +1,302 @@ +#!/bin/bash +# 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; version 2 of the License. +# +# 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. + +source @pkgdatadir@/common.sh + +CHROOT_VERSION='v2' + +FORCE='n' +RUN='' +NOCOPY='n' +NONETWORK='n' + +working_dir='' + +APPNAME=$(basename "${0}") + +# usage: usage +usage() { + echo "Usage: ${APPNAME} [options] working-dir [action]" + echo ' options:' + echo ' -f Force overwrite of files in the working-dir' + echo ' -C Location of a pacman config file' + echo ' -M Location of a makepkg config file' + echo ' -n Do not copy config files into the chroot' + echo ' -c Set pacman cache' + echo ' -N Disable networking in the chroot' + echo ' actions:' + echo ' -i Install "pkg-list" in the chroot.' + echo ' Creates the chroot if necessary, workding-dir must exist' + echo ' -r Run "cmd" within the context of the chroot' + echo ' -u Update the chroot via pacman' + echo ' -h Print this message' + + exit ${1-1} +} + +################################################################################ + +while getopts 'fC:M:nc:Nh' arg; do + case "${arg}" in + f) FORCE='y' ;; + C) pac_conf="$OPTARG" ;; + M) makepkg_conf="$OPTARG" ;; + n) NOCOPY='y' ;; + c) cache_dir="$OPTARG" ;; + N) NONETWORK='y' ;; + + h) action="-$arg" ;; + + *) error "invalid argument '${arg}'"; usage ;; + esac +done + +shift $(($OPTIND - 1)) + +if (( $# < 2 )); then + error 'You must specify a directory and an action.' + usage +fi + +working_dir="$(readlink -f "${1}")" +shift 1 +[[ -z $working_dir ]] && die 'Please specify a working directory.' + +action=$1 +shift 1 +case "$action" in + -i) PKGS=("$@") ;; + -r) RUN="$*" ;; + -u) + (( $# > 0 )) && { error 'Extra arguments.'; usage; } + RUN='/bin/sh -c "pacman -Syu --noconfirm && (pacman -Qqu >/dev/null && pacman -Su --noconfirm || exit 0)"' + ;; + -h) usage 0 ;; + -*) error "invalid argument '${action#-}'"; usage ;; + *) error "invalid action '${action}'"; usage ;; +esac +unset action + +################################################################################ + +if (( $EUID != 0 )); then + die 'This script must be run as root.' +fi + +if [[ -z $cache_dir ]]; then + cache_dirs=($(pacman -v $cache_conf 2>&1 | grep '^Cache Dirs:' | sed 's/Cache Dirs:\s*//g')) +else + cache_dirs=(${cache_dir}) +fi + +host_mirror=$(pacman -Sddp extra/devtools 2>/dev/null | sed -E 's#(.*/)extra/os/.*#\1$repo/os/$arch#') +if echo "${host_mirror}" | grep -q 'file://'; then + host_mirror_path=$(echo "${host_mirror}" | sed -E 's#file://(/.*)/\$repo/os/\$arch#\1#g') +fi + +# {{{ functions +bind_mount() { + local mode="${2:-rw}" + local target="${working_dir}${1}" + + if [[ ! -e "$target" ]]; then + if [[ -d "$1" ]]; then + install -d "$target" + else + install -D /dev/null "$target" + fi + fi + + mount -o bind "$1" "$target" + mount -o remount,${mode},bind "$target" + mount --make-slave "$target" +} + +chroot_mount() { + trap 'trap_chroot_umount' EXIT INT QUIT TERM HUP + + if (( ! have_nspawn )); then + bind_mount /sys ro + + [[ -e "${working_dir}/proc" ]] || mkdir "${working_dir}/proc" + mount -t proc proc -o nosuid,noexec,nodev "${working_dir}/proc" + bind_mount /proc/sys ro + + [[ -e "${working_dir}/dev" ]] || mkdir "${working_dir}/dev" + mount -t tmpfs dev "${working_dir}/dev" -o mode=0755,size=10M,nosuid,strictatime + mknod -m 666 "${working_dir}/dev/null" c 1 3 + mknod -m 666 "${working_dir}/dev/zero" c 1 5 + mknod -m 600 "${working_dir}/dev/console" c 5 1 + mknod -m 644 "${working_dir}/dev/random" c 1 8 + mknod -m 644 "${working_dir}/dev/urandom" c 1 9 + mknod -m 666 "${working_dir}/dev/tty" c 5 0 + mknod -m 666 "${working_dir}/dev/ptmx" c 5 2 + mknod -m 666 "${working_dir}/dev/tty0" c 4 0 + mknod -m 666 "${working_dir}/dev/full" c 1 7 + mknod -m 666 "${working_dir}/dev/rtc0" c 254 0 + ln -s /proc/kcore "${working_dir}/dev/core" + ln -s /proc/self/fd "${working_dir}/dev/fd" + ln -s /proc/self/fd/0 "${working_dir}/dev/stdin" + ln -s /proc/self/fd/1 "${working_dir}/dev/stdout" + ln -s /proc/self/fd/2 "${working_dir}/dev/stderr" + + [[ -e "${working_dir}/dev/shm" ]] || mkdir "${working_dir}/dev/shm" + mount -t tmpfs shm "${working_dir}/dev/shm" -o nodev,nosuid,size=128M + + bind_mount /dev/pts + + [[ -e "${working_dir}/run" ]] || mkdir "${working_dir}/run" + mount -t tmpfs tmpfs "${working_dir}/run" -o mode=0755,nodev,nosuid,strictatime,size=64M + + for host_config in resolv.conf localtime; do + bind_mount /etc/$host_config ro + done + fi + + [[ -n $host_mirror_path ]] && bind_mount "$host_mirror_path" ro + + bind_mount "${cache_dirs[0]}" + for cache_dir in ${cache_dirs[@]:1}; do + bind_mount "$cache_dir" ro + done +} + +copy_hostconf () { + cp -a /etc/pacman.d/gnupg "${working_dir}/etc/pacman.d" + echo "Server = ${host_mirror}" > ${working_dir}/etc/pacman.d/mirrorlist + + if [[ -n $pac_conf && $NOCOPY = 'n' ]]; then + cp ${pac_conf} ${working_dir}/etc/pacman.conf + fi + + if [[ -n $makepkg_conf && $NOCOPY = 'n' ]]; then + cp ${makepkg_conf} ${working_dir}/etc/makepkg.conf + fi + + sed -r "s|^#?\\s*CacheDir.+|CacheDir = $(echo -n ${cache_dirs[@]})|g" -i ${working_dir}/etc/pacman.conf +} + +trap_unmount_err () { + error "Error unmounting" +} + +trap_chroot_umount () { + trap 'trap_unmount_err' INT QUIT TERM HUP EXIT + + for cache_dir in ${cache_dirs[@]}; do + umount "${working_dir}/${cache_dir}" + done + [[ -n $host_mirror_path ]] && umount "${working_dir}/${host_mirror_path}" + + if (( ! have_nspawn )); then + for host_config in resolv.conf localtime; do + umount "${working_dir}/etc/${host_config}" + done + umount "${working_dir}/proc/sys" + umount "${working_dir}/proc" + umount "${working_dir}/sys" + umount "${working_dir}/dev/pts" + umount "${working_dir}/dev/shm" + umount "${working_dir}/dev" + umount "${working_dir}/run" + fi + + trap 'trap_abort' INT QUIT TERM HUP + trap 'trap_exit' EXIT +} + +chroot_lock () { + lock_open_write 9 "${working_dir}" "Locking chroot" +} + +chroot_run() { + local dir=$1 + shift + if (( have_nspawn)); then + local nspawn_args=(-D "$dir") + if [[ $NONETWORK = y ]]; then + nspawn_args+=(--private-network) + fi + eval systemd-nspawn "${nspawn_args[@]}" -- "${@}" 2>/dev/null + else + local unshare_args=(-mui) + if [[ $NONETWORK = y ]]; then + unshare_args+=(-n) + fi + eval unshare "${unshare_args[@]}" -- chroot "${dir}" "${@}" + fi +} + +# }}} + +# use systemd-nspawn if we have it available and systemd is running +if type -P systemd-nspawn >/dev/null && mountpoint -q /sys/fs/cgroup/systemd; then + have_nspawn=1 +fi + +umask 0022 +if [[ -n $RUN ]]; then + # run chroot {{{ + #Sanity check + if [[ ! -f "${working_dir}/.arch-chroot" ]]; then + die "'${working_dir}' does not appear to be a Arch chroot." + elif [[ $(cat "${working_dir}/.arch-chroot") != ${CHROOT_VERSION} ]]; then + die "'${working_dir}' is not compatible with ${APPNAME} version ${CHROOT_VERSION}. Please rebuild." + fi + + chroot_lock + chroot_mount + copy_hostconf + + chroot_run "${working_dir}" ${RUN} + + # }}} +else + # {{{ build chroot + if [[ -e $working_dir && $FORCE = 'n' ]]; then + die "Working directory '${working_dir}' already exists - try using -f" + fi + + if { type -P btrfs && btrfs subvolume create "${working_dir}"; } &>/dev/null; then + chmod 0755 "${working_dir}" + fi + + chroot_lock + chroot_mount + + pacargs="${cache_dirs[@]/#/--cachedir=}" + if [[ -n $pac_conf ]]; then + pacargs="$pacargs --config=${pac_conf}" + fi + + if (( $# != 0 )); then + if [[ $FORCE = 'y' ]]; then + pacargs="$pacargs --force" + fi + if ! pacstrap -GMcd "${working_dir}" ${pacargs} "${PKGS[@]}"; then + die 'Failed to install all packages' + fi + fi + + if [[ -d "${working_dir}/lib/modules" ]]; then + chroot_run "${working_dir}" ldconfig + fi + + if [[ -e "${working_dir}/etc/locale.gen" ]]; then + sed -i 's@^#\(en_US\|de_DE\)\(\.UTF-8\)@\1\2@' "${working_dir}/etc/locale.gen" + chroot_run "${working_dir}" locale-gen + fi + echo 'LANG=C' > "${working_dir}/etc/locale.conf" + + copy_hostconf + + echo "${CHROOT_VERSION}" > "${working_dir}/.arch-chroot" + # }}} +fi diff --git a/zsh_completion.in b/zsh_completion.in new file mode 100644 index 0000000..ec07b3b --- /dev/null +++ b/zsh_completion.in @@ -0,0 +1,34 @@ +#compdef finddeps archroot + +_archbuild_args=( + '-c[Recreate the chroot before building]' + '-r[Create chroots in this directory]:base_dir:_files -/' +) + +_finddeps_args=( + '1:packages:_devtools_completions_all_packages' +) + +_archroot_args=( + '-r[Run a program within the context of the chroot]:app' + '-u[Update the chroot via pacman]' + '-f[Force overwrite of files in the working-dir]' + '-C[Location of a pacman config file]:pacman_config:_files' + '-M[Location of a makepkg config file]:makepkg_config:_files' + '-n[Do not copy config files into the chroot]' + '-c[Set pacman cache]:pacman_cache:_files -/' + '-h[Display usage]' +) + +_devtools_completions_all_packages() { + typeset -U packages + packages=($(_call_program packages pacman -Sql)) + compadd - "${(@)packages}" +} + +_devtools() { + local argname="_${service}_args[@]" + _arguments -s "${(P)argname}" +} + +_devtools -- cgit v1.2.2