pacman/scripts/makepkg.sh.in

3067 lines
80 KiB
Bash

#!/bin/bash
#
# makepkg - make packages compatible for use with pacman
# @configure_input@
#
# Copyright (c) 2006-2014 Pacman Development Team <pacman-dev@archlinux.org>
# Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
# Copyright (c) 2005 by Aurelien Foret <orelien@chez.com>
# Copyright (c) 2006 by Miklos Vajna <vmiklos@frugalware.org>
# Copyright (c) 2005 by Christian Hamar <krics@linuxforum.hu>
# Copyright (c) 2006 by Alex Smith <alex@alex-smith.me.uk>
# Copyright (c) 2006 by Andras Voroskoi <voroskoi@frugalware.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 2 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/>.
#
# makepkg uses quite a few external programs during its execution. You
# need to have at least the following installed for makepkg to function:
# awk, bsdtar (libarchive), bzip2, coreutils, fakeroot, file, find (findutils),
# gettext, gpg, grep, gzip, openssl, sed, tput (ncurses), xz
# gettext initialization
export TEXTDOMAIN='pacman-scripts'
export TEXTDOMAINDIR='@localedir@'
# file -i does not work on Mac OSX unless legacy mode is set
export COMMAND_MODE='legacy'
# Ensure CDPATH doesn't screw with our cd calls
unset CDPATH
# Ensure GREP_OPTIONS doesn't screw with our grep calls
unset GREP_OPTIONS
declare -r makepkg_version='@PACKAGE_VERSION@'
declare -r confdir='@sysconfdir@'
declare -r BUILDSCRIPT='@BUILDSCRIPT@'
declare -r startdir="$PWD"
LIBRARY=${LIBRARY:-'@libmakepkgdir@'}
packaging_options=('strip' 'docs' 'libtool' 'staticlibs' 'emptydirs' 'zipman' \
'purge' 'upx' 'debug')
other_options=('ccache' 'distcc' 'buildflags' 'makeflags')
splitpkg_overrides=('pkgver' 'pkgrel' 'epoch' 'pkgdesc' 'arch' 'url' 'license' \
'groups' 'depends' 'optdepends' 'provides' 'conflicts' \
'replaces' 'backup' 'options' 'install' 'changelog')
readonly -a packaging_options other_options splitpkg_overrides
known_hash_algos=('md5' 'sha1' 'sha224' 'sha256' 'sha384' 'sha512')
# Options
ASDEPS=0
ASROOT=0
BUILDFUNC=0
CHECKFUNC=0
CLEANBUILD=0
CLEANUP=0
DEP_BIN=0
FORCE=0
GENINTEG=0
HOLDVER=0
IGNOREARCH=0
INFAKEROOT=0
INSTALL=0
LOGGING=0
NEEDED=0
NOBUILD=0
NODEPS=0
NOEXTRACT=0
PKGFUNC=0
PKGLIST=()
PKGVERFUNC=0
PREPAREFUNC=0
REPKG=0
RMDEPS=0
SKIPCHECKSUMS=0
SKIPPGPCHECK=0
SIGNPKG=''
SPLITPKG=0
SOURCEONLY=0
VERIFYSOURCE=0
# Forces the pkgver of the current PKGBUILD. Used by the fakeroot call
# when dealing with svn/cvs/etc PKGBUILDs.
FORCE_VER=""
PACMAN_OPTS=
shopt -s extglob
### SUBROUTINES ###
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}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
error() {
local mesg=$1; shift
printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}
##
# Special exit call for traps, Don't print any error messages when inside,
# the fakeroot call, the error message will be printed by the main call.
##
trap_exit() {
local signal=$1; shift
if (( ! INFAKEROOT )); then
echo
error "$@"
fi
[[ -n $srclinks ]] && rm -rf "$srclinks"
# unset the trap for this signal, and then call the default handler
trap -- "$signal"
kill "-$signal" "$$"
}
##
# Clean up function. Called automatically when the script exits.
##
clean_up() {
local EXIT_CODE=$?
if (( INFAKEROOT )); then
# Don't clean up when leaving fakeroot, we're not done yet.
return
fi
if (( ! EXIT_CODE && CLEANUP )); then
local pkg file
# If it's a clean exit and -c/--clean has been passed...
msg "$(gettext "Cleaning up...")"
rm -rf "$pkgdirbase" "$srcdir"
if [[ -n $pkgbase ]]; then
local fullver=$(get_full_version)
# Can't do this unless the BUILDSCRIPT has been sourced.
if (( BUILDFUNC )); then
rm -f "${pkgbase}-${fullver}-${CARCH}-build.log"*
fi
if (( CHECKFUNC )); then
rm -f "${pkgbase}-${fullver}-${CARCH}-check.log"*
fi
if (( PKGFUNC )); then
rm -f "${pkgbase}-${fullver}-${CARCH}-package.log"*
elif (( SPLITPKG )); then
for pkg in ${pkgname[@]}; do
rm -f "${pkgbase}-${fullver}-${CARCH}-package_${pkg}.log"*
done
fi
# clean up dangling symlinks to packages
for pkg in ${pkgname[@]}; do
for file in ${pkg}-*-*-*{${PKGEXT},${SRCEXT}}; do
if [[ -h $file && ! -e $file ]]; then
rm -f "$file"
fi
done
done
fi
fi
remove_deps
}
enter_fakeroot() {
msg "$(gettext "Entering %s environment...")" "fakeroot"
fakeroot -- $0 -F "${ARGLIST[@]}" || exit $?
}
# a source entry can have two forms :
# 1) "filename::http://path/to/file"
# 2) "http://path/to/file"
# Return the absolute filename of a source entry
get_filepath() {
local file="$(get_filename "$1")"
local proto="$(get_protocol "$1")"
case $proto in
bzr*|git*|hg*|svn*)
if [[ -d "$startdir/$file" ]]; then
file="$startdir/$file"
elif [[ -d "$SRCDEST/$file" ]]; then
file="$SRCDEST/$file"
else
return 1
fi
;;
*)
if [[ -f "$startdir/$file" ]]; then
file="$startdir/$file"
elif [[ -f "$SRCDEST/$file" ]]; then
file="$SRCDEST/$file"
else
return 1
fi
;;
esac
printf "%s\n" "$file"
}
# extract the filename from a source entry
get_filename() {
local netfile=$1
# if a filename is specified, use it
if [[ $netfile = *::* ]]; then
printf "%s\n" ${netfile%%::*}
return
fi
local proto=$(get_protocol "$netfile")
case $proto in
bzr*|git*|hg*|svn*)
filename=${netfile%%#*}
filename=${filename%/}
filename=${filename##*/}
if [[ $proto = bzr* ]]; then
filename=${filename#*lp:}
fi
if [[ $proto = git* ]]; then
filename=${filename%%.git*}
fi
;;
*)
# if it is just an URL, we only keep the last component
filename="${netfile##*/}"
;;
esac
printf "%s\n" "${filename}"
}
# extract the URL from a source entry
get_url() {
# strip an eventual filename
printf "%s\n" "${1#*::}"
}
# extract the protocol from a source entry - return "local" for local sources
get_protocol() {
if [[ $1 = *://* ]]; then
# strip leading filename
local proto="${1##*::}"
printf "%s\n" "${proto%%://*}"
elif [[ $1 = *lp:* ]]; then
local proto="${1##*::}"
printf "%s\n" "${proto%%lp:*}"
else
printf "%s\n" local
fi
}
get_downloadclient() {
local proto=$1
# loop through DOWNLOAD_AGENTS variable looking for protocol
local i
for i in "${DLAGENTS[@]}"; do
local handler="${i%%::*}"
if [[ $proto = "$handler" ]]; then
local agent="${i##*::}"
break
fi
done
# if we didn't find an agent, return an error
if [[ -z $agent ]]; then
error "$(gettext "Unknown download protocol: %s")" "$proto"
plain "$(gettext "Aborting...")"
exit 1 # $E_CONFIG_ERROR
fi
# ensure specified program is installed
local program="${agent%% *}"
if [[ ! -x $program ]]; then
local baseprog="${program##*/}"
error "$(gettext "The download program %s is not installed.")" "$baseprog"
plain "$(gettext "Aborting...")"
exit 1 # $E_MISSING_PROGRAM
fi
printf "%s\n" "$agent"
}
download_local() {
local netfile=$1
local filepath=$(get_filepath "$netfile")
if [[ -n "$filepath" ]]; then
msg2 "$(gettext "Found %s")" "${filepath##*/}"
else
local filename=$(get_filename "$netfile")
error "$(gettext "%s was not found in the build directory and is not a URL.")" "$filename"
exit 1 # $E_MISSING_FILE
fi
}
download_file() {
local netfile=$1
local filepath=$(get_filepath "$netfile")
if [[ -n "$filepath" ]]; then
msg2 "$(gettext "Found %s")" "${filepath##*/}"
return
fi
local proto=$(get_protocol "$netfile")
# find the client we should use for this URL
local dlcmd
dlcmd=$(get_downloadclient "$proto") || exit $?
local filename=$(get_filename "$netfile")
local url=$(get_url "$netfile")
if [[ $proto = "scp" ]]; then
# scp downloads should not pass the protocol in the url
url="${url##*://}"
fi
msg2 "$(gettext "Downloading %s...")" "$filename"
# temporary download file, default to last component of the URL
local dlfile="${url##*/}"
# replace %o by the temporary dlfile if it exists
if [[ $dlcmd = *%o* ]]; then
dlcmd=${dlcmd//\%o/\"$filename.part\"}
dlfile="$filename.part"
fi
# add the URL, either in place of %u or at the end
if [[ $dlcmd = *%u* ]]; then
dlcmd=${dlcmd//\%u/\"$url\"}
else
dlcmd="$dlcmd \"$url\""
fi
local ret=0
eval "$dlcmd >&2 || ret=\$?"
if (( ret )); then
[[ ! -s $dlfile ]] && rm -f -- "$dlfile"
error "$(gettext "Failure while downloading %s")" "$filename"
plain "$(gettext "Aborting...")"
exit 1
fi
# rename the temporary download file to the final destination
if [[ $dlfile != "$filename" ]]; then
mv -f "$SRCDEST/$dlfile" "$SRCDEST/$filename"
fi
}
extract_file() {
local file=$1
local filepath=$(get_filepath "$file")
rm -f "$srcdir/${file}"
ln -s "$filepath" "$srcdir/"
# do not rely on extension for file type
local file_type=$(file -bizL "$file")
local ext=${file##*.}
local cmd=''
case "$file_type" in
*application/x-tar*|*application/zip*|*application/x-zip*|*application/x-cpio*)
cmd="bsdtar" ;;
*application/x-gzip*)
case "$ext" in
gz|z|Z) cmd="gzip" ;;
*) return;;
esac ;;
*application/x-bzip*)
case "$ext" in
bz2|bz) cmd="bzip2" ;;
*) return;;
esac ;;
*application/x-xz*)
case "$ext" in
xz) cmd="xz" ;;
*) return;;
esac ;;
*)
# See if bsdtar can recognize the file
if bsdtar -tf "$file" -q '*' &>/dev/null; then
cmd="bsdtar"
else
return 0
fi ;;
esac
local ret=0
msg2 "$(gettext "Extracting %s with %s")" "$file" "$cmd"
if [[ $cmd = "bsdtar" ]]; then
$cmd -xf "$file" || ret=$?
else
rm -f -- "${file%.*}"
$cmd -dcf "$file" > "${file%.*}" || ret=$?
fi
if (( ret )); then
error "$(gettext "Failed to extract %s")" "$file"
plain "$(gettext "Aborting...")"
exit 1
fi
if (( EUID == 0 )); then
# change perms of all source files to root user & root group
chown -R 0:0 "$srcdir"
fi
}
download_bzr() {
local netfile=$1
local url=$(get_url "$netfile")
url=${url##*bzr+}
url=${url%%#*}
local repo=$(get_filename "$netfile")
local displaylocation="$url"
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
msg2 "$(gettext "Branching %s ...")" "${displaylocation}"
if ! bzr branch "$url" "$dir" --no-tree --use-existing-dir; then
error "$(gettext "Failure while branching %s")" "${displaylocation}"
plain "$(gettext "Aborting...")"
exit 1
fi
elif (( ! HOLDVER )); then
# Make sure we are fetching the right repo
local distant_url="$(bzr info $url 2> /dev/null | sed -n '/branch root/{s/ branch root: //p;q;}')"
local local_url="$(bzr config parent_location -d $dir)"
if [[ -n $distant_url ]]; then
if [[ $distant_url != "$local_url" ]]; then
error "$(gettext "%s is not a branch of %s")" "$dir" "$url"
plain "$(gettext "Aborting...")"
exit 1
fi
else
if [[ $url != "$local_url" ]] ; then
error "$(gettext "%s is not a branch of %s")" "$dir" "$url"
error "$(gettext "The local URL is %s")" "$local_url"
plain "$(gettext "Aborting...")"
exit 1
fi
fi
msg2 "$(gettext "Pulling %s ...")" "${displaylocation}"
cd_safe "$dir"
if ! bzr pull "$url" --overwrite; then
# only warn on failure to allow offline builds
warning "$(gettext "Failure while pulling %s")" "${displaylocation}"
fi
fi
}
extract_bzr() {
local netfile=$1
local repo=$(get_filename "$netfile")
local fragment=${netfile#*#}
if [[ $fragment = "$netfile" ]]; then
unset fragment
fi
if [[ -n $fragment ]]; then
case ${fragment%%=*} in
revision)
revision=("-r" "${fragment#*=}")
displaylocation="$url -r ${fragment#*=}"
;;
*)
error "$(gettext "Unrecognized reference: %s")" "${fragment}"
plain "$(gettext "Aborting...")"
exit 1
esac
fi
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
msg2 "$(gettext "Creating working copy of %s %s repo...")" "${repo}" "bzr"
pushd "$srcdir" &>/dev/null
rm -rf "${dir##*/}"
if ! { bzr checkout "$dir" "${revision[@]}" --lightweight &&
( cd "$repo" && bzr pull "$dir" -q --overwrite "${revision[@]}" ); }; then
error "$(gettext "Failure while creating working copy of %s %s repo")" "${repo}" "bzr"
plain "$(gettext "Aborting...")"
exit 1
fi
popd &>/dev/null
}
download_git() {
local netfile=$1
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
local repo=$(get_filename "$netfile")
local url=$(get_url "$netfile")
url=${url##*git+}
url=${url%%#*}
if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
msg2 "$(gettext "Cloning %s %s repo...")" "${repo}" "git"
if ! git clone --mirror "$url" "$dir"; then
error "$(gettext "Failure while downloading %s %s repo")" "${repo}" "git"
plain "$(gettext "Aborting...")"
exit 1
fi
elif (( ! HOLDVER )); then
cd_safe "$dir"
# Make sure we are fetching the right repo
if [[ "$url" != "$(git config --get remote.origin.url)" ]] ; then
error "$(gettext "%s is not a clone of %s")" "$dir" "$url"
plain "$(gettext "Aborting...")"
exit 1
fi
msg2 "$(gettext "Updating %s %s repo...")" "${repo}" "git"
if ! git fetch --all -p; then
# only warn on failure to allow offline builds
warning "$(gettext "Failure while updating %s %s repo")" "${repo}" "git"
fi
fi
}
extract_git() {
local netfile=$1
local fragment=${netfile#*#}
if [[ $fragment = "$netfile" ]]; then
unset fragment
fi
local repo=${netfile##*/}
repo=${repo%%#*}
repo=${repo%%.git*}
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
msg2 "$(gettext "Creating working copy of %s %s repo...")" "${repo}" "git"
pushd "$srcdir" &>/dev/null
rm -rf "${dir##*/}"
if ! git clone "$dir"; then
error "$(gettext "Failure while creating working copy of %s %s repo")" "${repo}" "git"
plain "$(gettext "Aborting...")"
exit 1
fi
cd_safe "${dir##*/}"
local ref
if [[ -n $fragment ]]; then
case ${fragment%%=*} in
commit|tag)
ref=${fragment##*=}
;;
branch)
ref=origin/${fragment##*=}
;;
*)
error "$(gettext "Unrecognized reference: %s")" "${fragment}"
plain "$(gettext "Aborting...")"
exit 1
esac
fi
if [[ -n $ref ]]; then
if ! git checkout -b makepkg $ref; then
error "$(gettext "Failure while creating working copy of %s %s repo")" "${repo}" "git"
plain "$(gettext "Aborting...")"
exit 1
fi
fi
popd &>/dev/null
}
download_hg() {
local netfile=$1
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
local repo=$(get_filename "$netfile")
local url=$(get_url "$netfile")
url=${url##*hg+}
url=${url%%#*}
if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
msg2 "$(gettext "Cloning %s %s repo...")" "${repo}" "hg"
if ! hg clone -U "$url" "$dir"; then
error "$(gettext "Failure while downloading %s %s repo")" "${repo}" "hg"
plain "$(gettext "Aborting...")"
exit 1
fi
elif (( ! HOLDVER )); then
msg2 "$(gettext "Updating %s %s repo...")" "${repo}" "hg"
cd_safe "$dir"
if ! hg pull; then
# only warn on failure to allow offline builds
warning "$(gettext "Failure while updating %s %s repo")" "${repo}" "hg"
fi
fi
}
extract_hg() {
local netfile=$1
local fragment=${netfile#*#}
if [[ $fragment = "$netfile" ]]; then
unset fragment
fi
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
local repo=${netfile##*/}
repo=${repo%%#*}
msg2 "$(gettext "Creating working copy of %s %s repo...")" "${repo}" "hg"
pushd "$srcdir" &>/dev/null
rm -rf "${dir##*/}"
local ref
if [[ -n $fragment ]]; then
case ${fragment%%=*} in
branch|revision|tag)
ref=('-u' "${fragment##*=}")
;;
*)
error "$(gettext "Unrecognized reference: %s")" "${fragment}"
plain "$(gettext "Aborting...")"
exit 1
esac
fi
if ! hg clone "${ref[@]}" "$dir" "${dir##*/}"; then
error "$(gettext "Failure while creating working copy of %s %s repo")" "${repo}" "hg"
plain "$(gettext "Aborting...")"
exit 1
fi
popd &>/dev/null
}
download_svn() {
local netfile=$1
local fragment=${netfile#*#}
if [[ $fragment = "$netfile" ]]; then
unset fragment
fi
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
local repo=$(get_filename "$netfile")
local url=$(get_url "$netfile")
if [[ $url != svn+ssh* ]]; then
url=${url##*svn+}
fi
url=${url%%#*}
if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
msg2 "$(gettext "Cloning %s %s repo...")" "${repo}" "svn"
mkdir -p "$dir/.makepkg"
if ! svn checkout --config-dir "$dir/.makepkg" "$url" "$dir"; then
error "$(gettext "Failure while downloading %s %s repo")" "${repo}" "svn"
plain "$(gettext "Aborting...")"
exit 1
fi
elif (( ! HOLDVER )); then
msg2 "$(gettext "Updating %s %s repo...")" "${repo}" "svn"
cd_safe "$dir"
if ! svn update; then
# only warn on failure to allow offline builds
warning "$(gettext "Failure while updating %s %s repo")" "${repo}" "svn"
fi
fi
}
extract_svn() {
local netfile=$1
local fragment=${netfile#*#}
if [[ $fragment = "$netfile" ]]; then
unset fragment
fi
local dir=$(get_filepath "$netfile")
[[ -z "$dir" ]] && dir="$SRCDEST/$(get_filename "$netfile")"
local repo=${netfile##*/}
repo=${repo%%#*}
msg2 "$(gettext "Creating working copy of %s %s repo...")" "${repo}" "svn"
pushd "$srcdir" &>/dev/null
rm -rf "${dir##*/}"
local ref
if [[ -n $fragment ]]; then
case ${fragment%%=*} in
revision)
ref="${fragment##*=}"
;;
*)
error "$(gettext "Unrecognized reference: %s")" "${fragment}"
plain "$(gettext "Aborting...")"
exit 1
esac
fi
cp -a "$dir" .
if [[ -n ${ref} ]]; then
cd_safe "$(get_filename "$netfile")"
if ! svn update -r ${ref}; then
error "$(gettext "Failure while creating working copy of %s %s repo")" "${repo}" "svn"
plain "$(gettext "Aborting...")"
fi
fi
popd &>/dev/null
}
download_sources() {
msg "$(gettext "Retrieving sources...")"
local GET_VCS=1
if [[ $1 == "fast" ]]; then
GET_VCS=0
fi
local netfile
for netfile in "${source[@]}"; do
pushd "$SRCDEST" &>/dev/null
local proto=$(get_protocol "$netfile")
case "$proto" in
local)
download_local "$netfile"
;;
bzr*)
(( GET_VCS )) && download_bzr "$netfile"
;;
git*)
(( GET_VCS )) && download_git "$netfile"
;;
hg*)
(( GET_VCS )) && download_hg "$netfile"
;;
svn*)
(( GET_VCS )) && download_svn "$netfile"
;;
*)
download_file "$netfile"
;;
esac
popd &>/dev/null
done
}
# Automatically update pkgver variable if a pkgver() function is provided
# Re-sources the PKGBUILD afterwards to allow for other variables that use $pkgver
update_pkgver() {
newpkgver=$(run_function_safe pkgver)
if ! validate_pkgver "$newpkgver"; then
error "$(gettext "pkgver() generated an invalid version: %s")" "$newpkgver"
exit 1
fi
if [[ -n $newpkgver && $newpkgver != "$pkgver" ]]; then
if [[ -f $BUILDFILE && -w $BUILDFILE ]]; then
if ! @SEDINPLACE@ "s:^pkgver=[^ ]*:pkgver=$newpkgver:" "$BUILDFILE"; then
error "$(gettext "Failed to update %s from %s to %s")" \
"pkgver" "$pkgver" "$newpkgver"
exit 1
fi
@SEDINPLACE@ "s:^pkgrel=[^ ]*:pkgrel=1:" "$BUILDFILE"
source_safe "$BUILDFILE"
local fullver=$(get_full_version)
msg "$(gettext "Updated version: %s")" "$pkgbase $fullver"
else
warning "$(gettext "%s is not writeable -- pkgver will not be updated")" \
"$BUILDFILE"
fi
fi
}
# Print 'source not found' error message and exit makepkg
missing_source_file() {
error "$(gettext "Unable to find source file %s.")" "$(get_filename "$1")"
plain "$(gettext "Aborting...")"
exit 1 # $E_MISSING_FILE
}
##
# usage : get_full_version( [$pkgname] )
# return : full version spec, including epoch (if necessary), pkgver, pkgrel
##
get_full_version() {
if [[ -z $1 ]]; then
if [[ $epoch ]] && (( ! $epoch )); then
printf "%s\n" "$pkgver-$pkgrel"
else
printf "%s\n" "$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
printf "%s\n" "$pkgver_override-$pkgrel_override"
else
printf "%s\n" "$epoch_override:$pkgver_override-$pkgrel_override"
fi
fi
}
##
# usage : get_pkg_arch( [$pkgname] )
# return : architecture of the package
##
get_pkg_arch() {
if [[ -z $1 ]]; then
if [[ $arch = "any" ]]; then
printf "%s\n" "any"
else
printf "%s\n" "$CARCH"
fi
else
local arch_override
eval $(declare -f package_$1 | sed -n 's/\(^[[:space:]]*arch=\)/arch_override=/p')
(( ${#arch_override[@]} == 0 )) && arch_override=("${arch[@]}")
if [[ $arch_override = "any" ]]; then
printf "%s\n" "any"
else
printf "%s\n" "$CARCH"
fi
fi
}
##
# Checks to see if options are present in makepkg.conf or PKGBUILD;
# PKGBUILD options always take precedence.
#
# usage : check_option( $option, $expected_val )
# return : 0 - matches expected
# 1 - does not match expected
# 127 - not found
##
check_option() {
in_opt_array "$1" ${options[@]}
case $? in
0) # assert enabled
[[ $2 = y ]]
return ;;
1) # assert disabled
[[ $2 = n ]]
return
esac
# fall back to makepkg.conf options
in_opt_array "$1" ${OPTIONS[@]}
case $? in
0) # assert enabled
[[ $2 = y ]]
return ;;
1) # assert disabled
[[ $2 = n ]]
return
esac
# not found
return 127
}
##
# Check if option is present in BUILDENV
#
# usage : check_buildenv( $option, $expected_val )
# return : 0 - matches expected
# 1 - does not match expected
# 127 - not found
##
check_buildenv() {
in_opt_array "$1" ${BUILDENV[@]}
case $? in
0) # assert enabled
[[ $2 = "y" ]]
return ;;
1) # assert disabled
[[ $2 = "n" ]]
return ;;
esac
# not found
return 127
}
##
# usage : in_opt_array( $needle, $haystack )
# return : 0 - enabled
# 1 - disabled
# 127 - not found
##
in_opt_array() {
local needle=$1; shift
local i opt
for (( i = $#; i > 0; i-- )); do
opt=${!i}
if [[ $opt = "$needle" ]]; then
# enabled
return 0
elif [[ $opt = "!$needle" ]]; then
# disabled
return 1
fi
done
# not found
return 127
}
##
# 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
}
source_has_signatures() {
local file
for file in "${source[@]}"; do
if [[ ${file%%::*} = *.@(sig?(n)|asc) ]]; then
return 0
fi
done
return 1
}
run_pacman() {
local cmd
if [[ ! $1 = -@(T|Qq) ]]; then
cmd=("$PACMAN_PATH" $PACMAN_OPTS "$@")
else
cmd=("$PACMAN_PATH" "$@")
fi
if (( ! ASROOT )) && [[ ! $1 = -@(T|Qq) ]]; then
if type -p sudo >/dev/null; then
cmd=(sudo "${cmd[@]}")
else
cmd=(su root -c "$(printf '%q ' "${cmd[@]}")")
fi
fi
"${cmd[@]}"
}
check_deps() {
(( $# > 0 )) || return 0
local ret=0
local pmout
pmout=$(run_pacman -T "$@")
ret=$?
if (( ret == 127 )); then #unresolved deps
printf "%s\n" "$pmout"
elif (( ret )); then
error "$(gettext "'%s' returned a fatal error (%i): %s")" "$PACMAN" "$ret" "$pmout"
return "$ret"
fi
}
handle_deps() {
local R_DEPS_SATISFIED=0
local R_DEPS_MISSING=1
(( $# == 0 )) && return $R_DEPS_SATISFIED
local deplist="$*"
if (( ! DEP_BIN )); then
return $R_DEPS_MISSING
fi
if (( DEP_BIN )); then
# install missing deps from binary packages (using pacman -S)
msg "$(gettext "Installing missing dependencies...")"
if ! run_pacman -S --asdeps $deplist; then
error "$(gettext "'%s' failed to install missing dependencies.")" "$PACMAN"
exit 1 # TODO: error code
fi
fi
# we might need the new system environment
# save our shell options and turn off extglob
local shellopts=$(shopt -p)
shopt -u extglob
source /etc/profile &>/dev/null
eval "$shellopts"
return $R_DEPS_SATISFIED
}
resolve_deps() {
local R_DEPS_SATISFIED=0
local R_DEPS_MISSING=1
# deplist cannot be declared like this: local deplist=$(foo)
# Otherwise, the return value will depend on the assignment.
local deplist
deplist="$(set +E; check_deps $*)" || exit 1
[[ -z $deplist ]] && return $R_DEPS_SATISFIED
if handle_deps $deplist; then
# check deps again to make sure they were resolved
deplist="$(set +E; check_deps $*)" || exit 1
[[ -z $deplist ]] && return $R_DEPS_SATISFIED
fi
msg "$(gettext "Missing dependencies:")"
local dep
for dep in $deplist; do
msg2 "$dep"
done
return $R_DEPS_MISSING
}
remove_deps() {
(( ! RMDEPS )) && return
# check for packages removed during dependency install (e.g. due to conflicts)
# removing all installed packages is risky in this case
if [[ -n $(grep -xvFf <(printf '%s\n' "${current_packagelist[@]}") \
<(printf '%s\n' "${original_packagelist[@]}") || true) ]]; then
warning "$(gettext "Failed to remove installed dependencies.")"
return 0
fi
local deplist
deplist=($(grep -xvFf <(printf "%s\n" "${original_pkglist[@]}") \
<(printf "%s\n" "${current_pkglist[@]}") || true))
if [[ -z $deplist ]]; then
return
fi
msg "Removing installed dependencies..."
# exit cleanly on failure to remove deps as package has been built successfully
if ! run_pacman -Rn ${deplist[@]}; then
warning "$(gettext "Failed to remove installed dependencies.")"
return 0
fi
}
get_integlist() {
local integ
local integlist=()
for integ in "${known_hash_algos[@]}"; do
local sumname="${integ}sums[@]"
if [[ -n ${!sumname} ]]; then
integlist+=("$integ")
fi
done
if (( ${#integlist[@]} > 0 )); then
printf "%s\n" "${integlist[@]}"
else
printf "%s\n" "${INTEGRITY_CHECK[@]}"
fi
}
generate_checksums() {
msg "$(gettext "Generating checksums for source files...")"
if ! type -p openssl >/dev/null; then
error "$(gettext "Cannot find the %s binary required for generating sourcefile checksums.")" "openssl"
exit 1 # $E_MISSING_PROGRAM
fi
local integlist
if (( $# == 0 )); then
IFS=$'\n' read -rd '' -a integlist < <(get_integlist)
else
integlist=("$@")
fi
local integ
for integ in "${integlist[@]}"; do
if ! in_array "$integ" "${known_hash_algos[@]}"; then
error "$(gettext "Invalid integrity algorithm '%s' specified.")" "$integ"
exit 1 # $E_CONFIG_ERROR
fi
local indentsz idx numsrc=${#source[@]}
printf "%s%n" "${integ}sums=(" indentsz
for (( idx = 0; idx < numsrc; i++ )); do
local netfile=${source[idx]}
local proto sum
proto="$(get_protocol "$netfile")"
case $proto in
bzr*|git*|hg*|svn*)
sum="SKIP"
;;
*)
if [[ ! $netfile = *.@(sig?(n)|asc) ]]; then
local file
file="$(get_filepath "$netfile")" || missing_source_file "$netfile"
sum="$(openssl dgst -${integ} "$file")"
sum=${sum##* }
else
sum="SKIP"
fi
;;
esac
# indent checksum on lines after the first
printf "%*s%s" $(( idx ? indentsz : 0 )) '' "'$sum'"
# print a newline on lines before the last
(( ++idx < numsrc )) && echo
done
echo ")"
done
}
check_checksums() {
(( SKIPCHECKSUMS )) && return 0
(( ! ${#source[@]} )) && return 0
local correlation=0
local integ required
for integ in "${known_hash_algos[@]}"; do
local sumname="${integ}sums[@]"
local integrity_sums=("${!sumname}")
if (( ${#integrity_sums[@]} == ${#source[@]} )); then
msg "$(gettext "Validating source files with %s...")" "${integ}sums"
correlation=1
local idx errors=0
for (( idx = 0; idx < ${#source[*]}; idx++ )); do
local file="$(get_filename "${source[idx]}")"
printf ' %s ... ' "$file" >&2
if [[ ${integrity_sums[idx]} = 'SKIP' ]]; then
printf '%s\n' "$(gettext "Skipped")" >&2
continue
fi
if ! file="$(get_filepath "$file")"; then
printf '%s\n' "$(gettext "NOT FOUND")" >&2
errors=1
continue
fi
local expectedsum="${integrity_sums[idx],,}"
local realsum="$(openssl dgst -${integ} "$file")"
realsum="${realsum##* }"
if [[ $expectedsum = "$realsum" ]]; then
printf '%s\n' "$(gettext "Passed")" >&2
else
printf '%s\n' "$(gettext "FAILED")" >&2
errors=1
fi
done
if (( errors )); then
error "$(gettext "One or more files did not pass the validity check!")"
exit 1 # TODO: error code
fi
elif (( ${#integrity_sums[@]} )); then
error "$(gettext "Integrity checks (%s) differ in size from the source array.")" "$integ"
exit 1 # TODO: error code
fi
done
if (( ! correlation )); then
error "$(gettext "Integrity checks are missing.")"
exit 1 # TODO: error code
fi
}
check_pgpsigs() {
(( SKIPPGPCHECK )) && return 0
! source_has_signatures && return 0
msg "$(gettext "Verifying source file signatures with %s...")" "gpg"
local file pubkey
local warning=0
local errors=0
local statusfile=$(mktemp)
for file in "${source[@]}"; do
file="$(get_filename "$file")"
if [[ ! $file = *.@(sig?(n)|asc) ]]; then
continue
fi
printf " %s ... " "${file%.*}" >&2
if ! file="$(get_filepath "$file")"; then
printf '%s\n' "$(gettext "SIGNATURE NOT FOUND")" >&2
errors=1
continue
fi
if ! sourcefile="$(get_filepath "${file%.*}")"; then
printf '%s\n' "$(gettext "SOURCE FILE NOT FOUND")" >&2
errors=1
continue
fi
if ! gpg --quiet --batch --status-file "$statusfile" --verify "$file" "$sourcefile" 2> /dev/null; then
printf '%s' "$(gettext "FAILED")" >&2
if ! pubkey=$(awk '/NO_PUBKEY/ { print $3; exit 1; }' "$statusfile"); then
printf ' (%s)' "$(gettext "unknown public key") $pubkey" >&2
warnings=1
else
errors=1
fi
printf '\n' >&2
else
if grep -q "REVKEYSIG" "$statusfile"; then
printf '%s (%s)' "$(gettext "FAILED")" "$(gettext "the key has been revoked.")" >&2
errors=1
else
printf '%s' "$(gettext "Passed")" >&2
if grep -q "EXPSIG" "$statusfile"; then
printf ' (%s)' "$(gettext "WARNING:") $(gettext "the signature has expired.")" >&2
warnings=1
elif grep -q "EXPKEYSIG" "$statusfile"; then
printf ' (%s)' "$(gettext "WARNING:") $(gettext "the key has expired.")" >&2
warnings=1
fi
fi
printf '\n' >&2
fi
done
rm -f "$statusfile"
if (( errors )); then
error "$(gettext "One or more PGP signatures could not be verified!")"
exit 1
fi
if (( warnings )); then
warning "$(gettext "Warnings have occurred while verifying the signatures.")"
plain "$(gettext "Please make sure you really trust them.")"
fi
}
check_source_integrity() {
if (( SKIPCHECKSUMS && SKIPPGPCHECK )); then
warning "$(gettext "Skipping all source file integrity checks.")"
elif (( SKIPCHECKSUMS )); then
warning "$(gettext "Skipping verification of source file checksums.")"
check_pgpsigs
elif (( SKIPPGPCHECK )); then
warning "$(gettext "Skipping verification of source file PGP signatures.")"
check_checksums
else
check_checksums
check_pgpsigs
fi
}
extract_sources() {
msg "$(gettext "Extracting sources...")"
local netfile
for netfile in "${source[@]}"; do
local file=$(get_filename "$netfile")
if in_array "$file" "${noextract[@]}"; then
# skip source files in the noextract=() array
# these are marked explicitly to NOT be extracted
continue
fi
local proto=$(get_protocol "$netfile")
case "$proto" in
bzr*)
extract_bzr "$netfile"
;;
git*)
extract_git "$netfile"
;;
hg*)
extract_hg "$netfile"
;;
svn*)
extract_svn "$netfile"
;;
*)
extract_file "$file"
;;
esac
done
}
error_function() {
if [[ -p $logpipe ]]; then
rm "$logpipe"
fi
# first exit all subshells, then print the error
if (( ! BASH_SUBSHELL )); then
error "$(gettext "A failure occurred in %s().")" "$1"
plain "$(gettext "Aborting...")"
fi
exit 2 # $E_BUILD_FAILED
}
cd_safe() {
if ! cd "$1"; then
error "$(gettext "Failed to change to directory %s")" "$1"
plain "$(gettext "Aborting...")"
exit 1
fi
}
source_safe() {
shopt -u extglob
if ! source "$@"; then
error "$(gettext "Failed to source %s")" "$1"
exit 1
fi
shopt -s extglob
}
run_function_safe() {
local restoretrap
set -e
set -E
restoretrap=$(trap -p ERR)
trap 'error_function $pkgfunc' ERR
run_function "$1"
eval $restoretrap
set +E
set +e
}
run_function() {
if [[ -z $1 ]]; then
return 1
fi
local pkgfunc="$1"
# clear user-specified buildflags if requested
if check_option "buildflags" "n"; then
unset CPPFLAGS CFLAGS CXXFLAGS LDFLAGS
fi
if check_option "debug" "y"; then
CFLAGS+=" $DEBUG_CFLAGS"
CXXFLAGS+=" $DEBUG_CXXFLAGS"
fi
# clear user-specified makeflags if requested
if check_option "makeflags" "n"; then
unset MAKEFLAGS
fi
msg "$(gettext "Starting %s()...")" "$pkgfunc"
cd_safe "$srcdir"
# ensure all necessary build variables are exported
export CPPFLAGS CFLAGS CXXFLAGS LDFLAGS MAKEFLAGS CHOST
# save our shell options so pkgfunc() can't override what we need
local shellopts=$(shopt -p)
local ret=0
if (( LOGGING )); then
local fullver=$(get_full_version)
local BUILDLOG="$LOGDEST/${pkgbase}-${fullver}-${CARCH}-$pkgfunc.log"
if [[ -f $BUILDLOG ]]; then
local i=1
while true; do
if [[ -f $BUILDLOG.$i ]]; then
i=$(($i +1))
else
break
fi
done
mv "$BUILDLOG" "$BUILDLOG.$i"
fi
# ensure overridden package variables survive tee with split packages
logpipe=$(mktemp -u "$LOGDEST/logpipe.XXXXXXXX")
mkfifo "$logpipe"
tee "$BUILDLOG" < "$logpipe" &
local teepid=$!
$pkgfunc &>"$logpipe"
wait $teepid
rm "$logpipe"
else
"$pkgfunc"
fi
# reset our shell options
eval "$shellopts"
}
run_prepare() {
run_function_safe "prepare"
}
run_build() {
# use distcc if it is requested (check buildenv and PKGBUILD opts)
if check_buildenv "distcc" "y" && ! check_option "distcc" "n"; then
[[ -d /usr/lib/distcc/bin ]] && export PATH="/usr/lib/distcc/bin:$PATH"
export DISTCC_HOSTS
fi
# use ccache if it is requested (check buildenv and PKGBUILD opts)
if check_buildenv "ccache" "y" && ! check_option "ccache" "n"; then
[[ -d /usr/lib/ccache/bin ]] && export PATH="/usr/lib/ccache/bin:$PATH"
fi
run_function_safe "build"
}
run_check() {
run_function_safe "check"
}
run_package() {
local pkgfunc
if [[ -z $1 ]]; then
pkgfunc="package"
else
pkgfunc="package_$1"
fi
run_function_safe "$pkgfunc"
}
build_id() {
LANG=C readelf -n $1 | sed -n '/Build ID/ { s/.*: //p; q; }'
}
strip_file() {
local binary=$1; shift
if check_option "debug" "y"; then
local bid=$(build_id "$binary")
# has this file already been stripped
if [[ -n "$bid" ]]; then
if [[ -f "$dbgdir/.build_id/${bid:0:2}/${bid:2}.debug" ]]; then
return
fi
elif [[ -f "$dbgdir/$binary.debug" ]]; then
return
fi
mkdir -p "$dbgdir/${binary%/*}"
objcopy --only-keep-debug "$binary" "$dbgdir/$binary.debug"
objcopy --add-gnu-debuglink="$dbgdir/${binary#/}.debug" "$binary"
# create any needed hardlinks
while read -rd '' file ; do
if [[ "${binary}" -ef "${file}" && ! -f "$dbgdir/${file}.debug" ]]; then
mkdir -p "$dbgdir/${file%/*}"
ln "$dbgdir/${binary}.debug" "$dbgdir/${file}.debug"
fi
done < <(find . -type f -perm -u+w -print0 2>/dev/null)
if [[ -n "$bid" ]]; then
local target
mkdir -p "$dbgdir/.build_id/${bid:0:2}"
target="../../../../../${binary#./}"
target="${target/..\/..\/usr\/lib\/}"
target="${target/..\/usr\/}"
ln -s "$target" "$dbgdir/.build_id/${bid:0:2}/${bid:2}"
target="../../${binary#./}.debug"
ln -s "$target" "$dbgdir/.build_id/${bid:0:2}/${bid:2}.debug"
fi
fi
strip $@ "$binary"
}
tidy_install() {
cd_safe "$pkgdir"
msg "$(gettext "Tidying install...")"
if check_option "docs" "n" && [[ -n ${DOC_DIRS[*]} ]]; then
msg2 "$(gettext "Removing doc files...")"
rm -rf -- ${DOC_DIRS[@]}
fi
if check_option "purge" "y" && [[ -n ${PURGE_TARGETS[*]} ]]; then
msg2 "$(gettext "Purging unwanted files...")"
local pt
for pt in "${PURGE_TARGETS[@]}"; do
if [[ ${pt} = "${pt//\/}" ]]; then
find . ! -type d -name "${pt}" -exec rm -f -- '{}' +
else
rm -f ${pt}
fi
done
fi
if check_option "libtool" "n"; then
msg2 "$(gettext "Removing "%s" files...")" "libtool"
find . ! -type d -name "*.la" -exec rm -f -- '{}' +
fi
if check_option "staticlibs" "n"; then
msg2 "$(gettext "Removing static library files...")"
local l
while read -rd '' l; do
if [[ -f "${l%.a}.so" ]]; then
rm "$l"
fi
done < <(find . ! -type d -name "*.a" -print0)
fi
if check_option "emptydirs" "n"; then
msg2 "$(gettext "Removing empty directories...")"
find . -depth -type d -exec rmdir '{}' + 2>/dev/null
fi
# check existence of backup files
local file
for file in "${backup[@]}"; do
if [[ ! -f $file ]]; then
warning "$(gettext "%s entry file not in package : %s")" "backup" "$file"
fi
done
# check for references to the build and package directory
if find "${pkgdir}" -type f -print0 | xargs -0 grep -q -I "${srcdir}" ; then
warning "$(gettext "Package contains reference to %s")" "\$srcdir"
fi
if find "${pkgdir}" -type f -print0 | xargs -0 grep -q -I "${pkgdirbase}" ; then
warning "$(gettext "Package contains reference to %s")" "\$pkgdir"
fi
if check_option "zipman" "y" && [[ -n ${MAN_DIRS[*]} ]]; then
msg2 "$(gettext "Compressing man and info pages...")"
local file files inode link
while read -rd ' ' inode; do
read file
find ${MAN_DIRS[@]} -type l 2>/dev/null |
while read -r link ; do
if [[ "${file}" -ef "${link}" ]] ; then
rm -f "$link" "${link}.gz"
if [[ ${file%/*} = ${link%/*} ]]; then
ln -s -- "${file##*/}.gz" "${link}.gz"
else
ln -s -- "/${file}.gz" "${link}.gz"
fi
fi
done
if [[ -z ${files[$inode]} ]]; then
files[$inode]=$file
gzip -9 -n -f "$file"
else
rm -f "$file"
ln "${files[$inode]}.gz" "${file}.gz"
chmod 644 "${file}.gz"
fi
done < <(find ${MAN_DIRS[@]} -type f \! -name "*.gz" \! -name "*.bz2" \
-exec @INODECMD@ '{}' + 2>/dev/null)
fi
if check_option "strip" "y"; then
msg2 "$(gettext "Stripping unneeded symbols from binaries and libraries...")"
# make sure library stripping variables are defined to prevent excess stripping
[[ -z ${STRIP_SHARED+x} ]] && STRIP_SHARED="-S"
[[ -z ${STRIP_STATIC+x} ]] && STRIP_STATIC="-S"
if check_option "debug" "y"; then
dbgdir="$pkgdir-@DEBUGSUFFIX@/usr/lib/debug"
mkdir -p "$dbgdir"
fi
local binary strip_flags
find . -type f -perm -u+w -print0 2>/dev/null | while read -rd '' binary ; do
case "$(file -bi "$binary")" in
*application/x-sharedlib*) # Libraries (.so)
strip_flags="$STRIP_SHARED";;
*application/x-archive*) # Libraries (.a)
strip_flags="$STRIP_STATIC";;
*application/x-executable*) # Binaries
strip_flags="$STRIP_BINARIES";;
*)
continue ;;
esac
strip_file "$binary" ${strip_flags}
done
fi
if check_option "upx" "y"; then
msg2 "$(gettext "Compressing binaries with %s...")" "UPX"
local binary
find . -type f -perm -u+w 2>/dev/null | while read -r binary ; do
if [[ $(file -bi "$binary") = *'application/x-executable'* ]]; then
upx $UPXFLAGS "$binary" &>/dev/null ||
warning "$(gettext "Could not compress binary : %s")" "${binary/$pkgdir\//}"
fi
done
fi
}
find_libdepends() {
local d sodepends;
sodepends=0;
for d in "${depends[@]}"; do
if [[ $d = *.so ]]; then
sodepends=1;
break;
fi
done
if (( sodepends == 0 )); then
printf '%s\n' "${depends[@]}"
return;
fi
local libdeps filename soarch sofile soname soversion;
declare -A libdeps;
while read -r filename; do
# 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
# 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
# extract the library name: libfoo.so
soname="${sofile%.so?(+(.+([0-9])))}".so
# extract the major version: 1
soversion="${sofile##*\.so\.}"
if [[ ${libdeps[$soname]} ]]; then
if [[ ${libdeps[$soname]} != *${soversion}-${soarch}* ]]; then
libdeps[$soname]+=" ${soversion}-${soarch}"
fi
else
libdeps[$soname]="${soversion}-${soarch}"
fi
done
done < <(find "$pkgdir" -type f -perm -u+x)
local libdepends v
for d in "${depends[@]}"; do
case "$d" in
*.so)
if [[ ${libdeps[$d]} ]]; then
for v in ${libdeps[$d]}; do
libdepends+=("$d=$v")
done
else
warning "$(gettext "Library listed in %s is not required by any files: %s")" "'depends'" "$d"
libdepends+=("$d")
fi
;;
*)
libdepends+=("$d")
;;
esac
done
printf '%s\n' "${libdepends[@]}"
}
find_libprovides() {
local p libprovides missing
for p in "${provides[@]}"; do
missing=0
case "$p" in
*.so)
mapfile -t filename < <(find "$pkgdir" -type f -name $p\*)
if [[ $filename ]]; then
# packages may provide multiple versions of the same library
for fn in "${filename[@]}"; do
# check if we really have a shared object
if LC_ALL=C readelf -h "$fn" 2>/dev/null | grep -q '.*Type:.*DYN (Shared object file).*'; then
# get the string binaries link to (e.g. libfoo.so.1.2 -> libfoo.so.1)
local sofile=$(LC_ALL=C readelf -d "$fn" 2>/dev/null | sed -n 's/.*Library soname: \[\(.*\)\].*/\1/p')
if [[ -z "$sofile" ]]; then
warning "$(gettext "Library listed in %s is not versioned: %s")" "'provides'" "$p"
libprovides+=("$p")
continue
fi
# get the library architecture (32 or 64 bit)
local soarch=$(LC_ALL=C readelf -h "$fn" | sed -n 's/.*Class.*ELF\(32\|64\)/\1/p')
# extract the library major version
local soversion="${sofile##*\.so\.}"
libprovides+=("${p}=${soversion}-${soarch}")
else
warning "$(gettext "Library listed in %s is not a shared object: %s")" "'provides'" "$p"
libprovides+=("$p")
fi
done
else
libprovides+=("$p")
missing=1
fi
;;
*)
libprovides+=("$p")
;;
esac
if (( missing )); then
warning "$(gettext "Cannot find library listed in %s: %s")" "'provides'" "$p"
fi
done
printf '%s\n' "${libprovides[@]}"
}
write_pkginfo() {
local builddate=$(date -u "+%s")
if [[ -n $PACKAGER ]]; then
local packager="$PACKAGER"
else
local packager="Unknown Packager"
fi
local size="$(@DUPATH@ @DUFLAGS@)"
size="$(( ${size%%[^0-9]*} * 1024 ))"
msg2 "$(gettext "Generating %s file...")" ".PKGINFO"
printf "# Generated by makepkg %s\n" "$makepkg_version"
(( INFAKEROOT )) && printf "# using %s\n" "$(fakeroot -v)"
printf "# %s\n" "$(LC_ALL=C date -u)"
printf "pkgname = %s\n" "$pkgname"
(( SPLITPKG )) && printf "pkgbase = %s\n" "$pkgbase"
printf "pkgver = %s\n" "$(get_full_version)"
printf "pkgdesc = %s\n" "${pkgdesc//+([[:space:]])/ }"
printf "url = %s\n" "$url"
printf "builddate = %s\n" "$builddate"
printf "packager = %s\n" "$packager"
printf "size = %s\n" "$size"
printf "arch = %s\n" "$pkgarch"
mapfile -t provides < <(find_libprovides)
mapfile -t depends < <(find_libdepends)
[[ $license ]] && printf "license = %s\n" "${license[@]}"
[[ $replaces ]] && printf "replaces = %s\n" "${replaces[@]}"
[[ $groups ]] && printf "group = %s\n" "${groups[@]}"
[[ $conflicts ]] && printf "conflict = %s\n" "${conflicts[@]}"
[[ $provides ]] && printf "provides = %s\n" "${provides[@]}"
[[ $backup ]] && printf "backup = %s\n" "${backup[@]}"
[[ $depends ]] && printf "depend = %s\n" "${depends[@]}"
[[ $optdepends ]] && printf "optdepend = %s\n" "${optdepends[@]//+([[:space:]])/ }"
[[ $makedepends ]] && printf "makedepend = %s\n" "${makedepends[@]}"
[[ $checkdepends ]] && printf "checkdepend = %s\n" "${checkdepends[@]}"
local it
for it in "${packaging_options[@]}"; do
check_option "$it" "y"
case $? in
0)
printf "makepkgopt = %s\n" "$it"
;;
1)
printf "makepkgopt = %s\n" "!$it"
;;
esac
done
}
create_package() {
if [[ ! -d $pkgdir ]]; then
error "$(gettext "Missing %s directory.")" "\$pkgdir/"
plain "$(gettext "Aborting...")"
exit 1 # $E_MISSING_PKGDIR
fi
cd_safe "$pkgdir"
msg "$(gettext "Creating package \"%s\"...")" "$pkgname"
pkgarch=$(get_pkg_arch)
write_pkginfo > .PKGINFO
local comp_files=('.PKGINFO')
# check for changelog/install files
for i in 'changelog/.CHANGELOG' 'install/.INSTALL'; do
IFS='/' read -r orig dest < <(printf '%s\n' "$i")
if [[ -n ${!orig} ]]; then
msg2 "$(gettext "Adding %s file...")" "$orig"
cp "$startdir/${!orig}" "$dest"
chmod 644 "$dest"
comp_files+=("$dest")
fi
done
# tar it up
local fullver=$(get_full_version)
local pkg_file="$PKGDEST/${pkgname}-${fullver}-${pkgarch}${PKGEXT}"
local ret=0
[[ -f $pkg_file ]] && rm -f "$pkg_file"
[[ -f $pkg_file.sig ]] && rm -f "$pkg_file.sig"
# when fileglobbing, we want * in an empty directory to expand to
# the null string rather than itself
shopt -s nullglob
msg2 "$(gettext "Generating .MTREE file...")"
bsdtar -czf .MTREE --format=mtree \
--options='!all,use-set,type,uid,gid,mode,time,size,md5,sha256,link' \
"${comp_files[@]}" *
comp_files+=(".MTREE")
msg2 "$(gettext "Compressing package...")"
# TODO: Maybe this can be set globally for robustness
shopt -s -o pipefail
# bsdtar's gzip compression always saves the time stamp, making one
# archive created using the same command line distinct from another.
# Disable bsdtar compression and use gzip -n for now.
bsdtar -cf - "${comp_files[@]}" * |
case "$PKGEXT" in
*tar.gz) ${COMPRESSGZ[@]:-gzip -c -f -n} ;;
*tar.bz2) ${COMPRESSBZ2[@]:-bzip2 -c -f} ;;
*tar.xz) ${COMPRESSXZ[@]:-xz -c -z -} ;;
*tar.lrz) ${COMPRESSLRZ[@]:-lrzip -q} ;;
*tar.lzo) ${COMPRESSLZO[@]:-lzop -q} ;;
*tar.Z) ${COMPRESSZ[@]:-compress -c -f} ;;
*tar) cat ;;
*) warning "$(gettext "'%s' is not a valid archive extension.")" \
"$PKGEXT"; cat ;;
esac > "${pkg_file}" || ret=$?
shopt -u nullglob
shopt -u -o pipefail
if (( ret )); then
error "$(gettext "Failed to create package file.")"
exit 1 # TODO: error code
fi
create_signature "$pkg_file"
if (( ! ret )) && [[ ! "$PKGDEST" -ef "${startdir}" ]]; then
rm -f "${pkg_file/$PKGDEST/$startdir}"
ln -s "${pkg_file}" "${pkg_file/$PKGDEST/$startdir}"
ret=$?
if [[ -f $pkg_file.sig ]]; then
rm -f "${pkg_file/$PKGDEST/$startdir}.sig"
ln -s "$pkg_file.sig" "${pkg_file/$PKGDEST/$startdir}.sig"
fi
fi
if (( ret )); then
warning "$(gettext "Failed to create symlink to package file.")"
fi
}
create_debug_package() {
# check if a debug package was requested
if ! check_option "debug" "y" || ! check_option "strip" "y"; then
return
fi
pkgdir="${pkgdir}-@DEBUGSUFFIX@"
# check if we have any debug symbols to package
if dir_is_empty "$pkgdir/usr/lib/debug"; then
return
fi
depends=("$pkgname=$(get_full_version)")
pkgdesc="Detached debugging symbols for $pkgname"
pkgname=$pkgname-@DEBUGSUFFIX@
unset groups optdepends provides conflicts replaces backup install changelog
create_package
}
create_signature() {
if [[ $SIGNPKG != 'y' ]]; then
return
fi
local ret=0
local filename="$1"
msg "$(gettext "Signing package...")"
local SIGNWITHKEY=""
if [[ -n $GPGKEY ]]; then
SIGNWITHKEY="-u ${GPGKEY}"
fi
# The signature will be generated directly in ascii-friendly format
gpg --detach-sign --use-agent ${SIGNWITHKEY} "$filename" &>/dev/null || ret=$?
if (( ! ret )); then
msg2 "$(gettext "Created signature file %s.")" "$filename.sig"
else
warning "$(gettext "Failed to sign package file.")"
fi
}
create_srcpackage() {
local ret=0
msg "$(gettext "Creating source package...")"
local srclinks="$(mktemp -d "$startdir"/srclinks.XXXXXXXXX)"
mkdir "${srclinks}"/${pkgbase}
msg2 "$(gettext "Adding %s...")" "$BUILDSCRIPT"
ln -s "${BUILDFILE}" "${srclinks}/${pkgbase}/${BUILDSCRIPT}"
local file
for file in "${source[@]}"; do
if [[ "$file" = "$(get_filename "$file")" ]] || (( SOURCEONLY == 2 )); then
local absfile
absfile=$(get_filepath "$file") || missing_source_file "$file"
msg2 "$(gettext "Adding %s...")" "${absfile##*/}"
ln -s "$absfile" "$srclinks/$pkgbase"
fi
done
local i
for i in 'changelog' 'install'; do
local file
while read -r file; do
# evaluate any bash variables used
eval file=\"$(sed 's/^\(['\''"]\)\(.*\)\1$/\2/' <<< "$file")\"
if [[ $file && ! -f "${srclinks}/${pkgbase}/$file" ]]; then
msg2 "$(gettext "Adding %s file (%s)...")" "$i" "${file}"
ln -s "${startdir}/$file" "${srclinks}/${pkgbase}/"
fi
done < <(sed -n "s/^[[:space:]]*$i=//p" "$BUILDFILE")
done
local TAR_OPT
case "$SRCEXT" in
*tar.gz) TAR_OPT="-z" ;;
*tar.bz2) TAR_OPT="-j" ;;
*tar.xz) TAR_OPT="-J" ;;
*tar.lrz) TAR_OPT="--lrzip" ;;
*tar.lzo) TAR_OPT="--lzop" ;;
*tar.Z) TAR_OPT="-Z" ;;
*tar) TAR_OPT="" ;;
*) warning "$(gettext "'%s' is not a valid archive extension.")" \
"$SRCEXT" ;;
esac
local fullver=$(get_full_version)
local pkg_file="$SRCPKGDEST/${pkgbase}-${fullver}${SRCEXT}"
# tar it up
msg2 "$(gettext "Compressing source package...")"
cd_safe "${srclinks}"
if ! bsdtar -cL ${TAR_OPT} -f "$pkg_file" ${pkgbase}; then
error "$(gettext "Failed to create source package file.")"
exit 1 # TODO: error code
fi
if [[ ! "$SRCPKGDEST" -ef "${startdir}" ]]; then
rm -f "${pkg_file/$SRCPKGDEST/$startdir}"
ln -s "${pkg_file}" "${pkg_file/$SRCPKGDEST/$startdir}"
ret=$?
fi
if (( ret )); then
warning "$(gettext "Failed to create symlink to source package file.")"
fi
cd_safe "${startdir}"
rm -rf "${srclinks}"
}
install_package() {
(( ! INSTALL )) && return
if (( ! SPLITPKG )); then
msg "$(gettext "Installing package %s with %s...")" "$pkgname" "$PACMAN -U"
else
msg "$(gettext "Installing %s package group with %s...")" "$pkgbase" "$PACMAN -U"
fi
local fullver pkgarch pkg pkglist
(( ASDEPS )) && pkglist+=('--asdeps')
(( NEEDED )) && pkglist+=('--needed')
for pkg in ${pkgname[@]}; do
fullver=$(get_full_version $pkg)
pkgarch=$(get_pkg_arch $pkg)
pkglist+=("$PKGDEST/${pkg}-${fullver}-${pkgarch}${PKGEXT}")
if [[ -f "$PKGDEST/${pkg}-debug-${fullver}-${pkgarch}${PKGEXT}" ]]; then
pkglist+=("$PKGDEST/${pkg}-debug-${fullver}-${pkgarch}${PKGEXT}")
fi
done
if ! run_pacman -U "${pkglist[@]}"; then
warning "$(gettext "Failed to install built package(s).")"
return 0
fi
}
have_function() {
declare -f "$1" >/dev/null
}
check_sanity() {
# check for no-no's in the build script
local i
local ret=0
for i in 'pkgname' 'pkgrel'; do
if [[ -z ${!i} ]]; then
error "$(gettext "%s is not allowed to be empty.")" "$i"
ret=1
fi
done
for i in "${pkgname[@]}"; do
if [[ ${i:0:1} = "-" ]]; then
error "$(gettext "%s is not allowed to start with a hyphen.")" "pkgname"
ret=1
fi
if [[ ${i:0:1} = "." ]]; then
error "$(gettext "%s is not allowed to start with a dot.")" "pkgname"
ret=1
fi
if [[ $i = *[^[:alnum:]+_.@-]* ]]; then
error "$(gettext "%s contains invalid characters: '%s'")" \
'pkgname' "${pkgname//[[:alnum:]+_.@-]}"
ret=1
fi
done
if [[ ${pkgbase:0:1} = "-" ]]; then
error "$(gettext "%s is not allowed to start with a hyphen.")" "pkgbase"
ret=1
fi
if (( ! PKGVERFUNC )) ; then
check_pkgver || ret=1
fi
awk -F'=' '$1 ~ /^[[:space:]]*pkgrel$/' "$BUILDFILE" | sed "s/[[:space:]]*#.*//" |
while IFS='=' read -r _ i; do
eval i=\"$(sed 's/^\(['\''"]\)\(.*\)\1$/\2/' <<< "${i%%+([[:space:]])}")\"
if [[ $i != +([0-9])?(.+([0-9])) ]]; then
error "$(gettext "%s must be a decimal.")" "pkgrel"
return 1
fi
done || ret=1
awk -F'=' '$1 ~ /^[[:space:]]*epoch$/' "$BUILDFILE" |
while IFS='=' read -r _ i; do
eval i=\"$(sed 's/^\(['\''"]\)\(.*\)\1$/\2/' <<< "${i%%+([[:space:]])}")\"
if [[ $i != *([[:digit:]]) ]]; then
error "$(gettext "%s must be an integer.")" "epoch"
return 1
fi
done || ret=1
if [[ $arch != 'any' ]]; then
if ! in_array $CARCH "${arch[@]}"; then
if (( ! IGNOREARCH )); then
error "$(gettext "%s is not available for the '%s' architecture.")" "$pkgbase" "$CARCH"
plain "$(gettext "Note that many packages may need a line added to their %s")" "$BUILDSCRIPT"
plain "$(gettext "such as %s.")" "arch=('$CARCH')"
ret=1
fi
fi
fi
if (( ${#pkgname[@]} > 1 )); then
for i in ${pkgname[@]}; do
local arch_list=""
eval $(declare -f package_${i} | sed -n 's/\(^[[:space:]]*arch=\)/arch_list=/p')
if [[ ${arch_list[@]} && ${arch_list} != 'any' ]]; then
if ! in_array $CARCH "${arch_list[@]}"; then
if (( ! IGNOREARCH )); then
error "$(gettext "%s is not available for the '%s' architecture.")" "$i" "$CARCH"
ret=1
fi
fi
fi
done
fi
local provides_list=()
eval $(awk '/^[[:space:]]*provides=/,/\)/' "$BUILDFILE" | \
sed -e "s/provides=/provides_list+=/" -e "s/#.*//" -e 's/\\$//')
for i in ${provides_list[@]}; do
if [[ $i == *['<>']* ]]; then
error "$(gettext "%s array cannot contain comparison (< or >) operators.")" "provides"
ret=1
fi
done
local backup_list=()
eval $(awk '/^[[:space:]]*backup=/,/\)/' "$BUILDFILE" | \
sed -e "s/backup=/backup_list+=/" -e "s/#.*//" -e 's/\\$//')
for i in "${backup_list[@]}"; do
if [[ ${i:0:1} = "/" ]]; then
error "$(gettext "%s entry should not contain leading slash : %s")" "backup" "$i"
ret=1
fi
done
local optdepends_list=()
eval $(awk '/^[[:space:]]*optdepends=\(/,/\)[[:space:]]*(#.*)?$/' "$BUILDFILE" | \
sed -e "s/optdepends=/optdepends_list+=/" -e "s/#.*//" -e 's/\\$//')
for i in "${optdepends_list[@]}"; do
local pkg=${i%%:[[:space:]]*}
# the '-' character _must_ be first or last in the character range
if [[ $pkg != +([-[:alnum:]><=.+_:]) ]]; then
error "$(gettext "Invalid syntax for %s : '%s'")" "optdepend" "$i"
ret=1
fi
done
for i in 'changelog' 'install'; do
local file
while read -r file; do
# evaluate any bash variables used
eval file=\"$(sed 's/^\(['\''"]\)\(.*\)\1$/\2/' <<< "$file")\"
if [[ $file && ! -f $file ]]; then
error "$(gettext "%s file (%s) does not exist.")" "$i" "$file"
ret=1
fi
done < <(sed -n "s/^[[:space:]]*$i=//p" "$BUILDFILE")
done
local valid_options=1
local known kopt options_list
eval $(awk '/^[[:space:]]*options=/,/\)/' "$BUILDFILE" | \
sed -e "s/options=/options_list+=/" -e "s/#.*//" -e 's/\\$//')
for i in ${options_list[@]}; do
known=0
# check if option matches a known option or its inverse
for kopt in ${packaging_options[@]} ${other_options[@]}; do
if [[ ${i} = "${kopt}" || ${i} = "!${kopt}" ]]; then
known=1
fi
done
if (( ! known )); then
error "$(gettext "%s array contains unknown option '%s'")" "options" "$i"
valid_options=0
fi
done
if (( ! valid_options )); then
ret=1
fi
if (( ${#pkgname[@]} == 1 )); then
if have_function build && ! ( have_function package || have_function package_${pkgname}); then
error "$(gettext "Missing %s function in %s")" "package()" "$BUILDFILE"
ret=1
fi
else
for i in ${pkgname[@]}; do
if ! have_function package_${i}; then
error "$(gettext "Missing %s function for split package '%s'")" "package_$i()" "$i"
ret=1
fi
done
fi
for i in ${PKGLIST[@]}; do
if ! in_array $i ${pkgname[@]}; then
error "$(gettext "Requested package %s is not provided in %s")" "$i" "$BUILDFILE"
ret=1
fi
done
local idx=("${!source[@]}")
if (( ${#source[*]} > 0 && (idx[-1] + 1) != ${#source[*]} )); then
error "$(gettext "Sparse arrays are not allowed for source")"
ret=1
fi
return $ret
}
validate_pkgver() {
if [[ $1 = *[[:space:]:-]* ]]; then
error "$(gettext "%s is not allowed to contain colons, hyphens or whitespace.")" "pkgver"
return 1
fi
}
check_pkgver() {
local ret=0
if [[ -z ${pkgver} ]]; then
error "$(gettext "%s is not allowed to be empty.")" "pkgver"
ret=1
fi
awk -F'=' '$1 ~ /^[[:space:]]*pkgver$/' "$BUILDFILE" | sed "s/[[:space:]]*#.*//" |
while IFS='=' read -r _ i; do
eval i=\"$(sed 's/^\(['\''"]\)\(.*\)\1$/\2/' <<< "${i%%+([[:space:]])}")\"
validate_pkgver "$i" || return 1
done || ret=1
return $ret
}
check_software() {
# check for needed software
local ret=0
# check for PACMAN if we need it
if (( ! INFAKEROOT && ( ! NODEPS || DEP_BIN || RMDEPS || INSTALL ) )); then
if [[ -z $PACMAN_PATH ]]; then
error "$(gettext "Cannot find the %s binary required for dependency operations.")" "$PACMAN"
ret=1
fi
fi
# check for sudo if we will need it during makepkg execution
if (( ! ( ASROOT || INFAKEROOT ) && ( DEP_BIN || RMDEPS || INSTALL ) )); then
if ! type -p sudo >/dev/null; then
warning "$(gettext "Cannot find the %s binary. Will use %s to acquire root privileges.")" "sudo" "su"
fi
fi
# fakeroot - building as non-root user
if check_buildenv "fakeroot" "y" && (( EUID > 0 )); then
if ! type -p fakeroot >/dev/null; then
error "$(gettext "Cannot find the %s binary required for building as non-root user.")" "fakeroot"
ret=1
fi
fi
# gpg - package signing
if [[ $SIGNPKG == 'y' ]] || { [[ -z $SIGNPKG ]] && check_buildenv "sign" "y"; }; then
if ! type -p gpg >/dev/null; then
error "$(gettext "Cannot find the %s binary required for signing packages.")" "gpg"
ret=1
fi
fi
# gpg - source verification
if (( ! SKIPPGPCHECK )) && source_has_signatures; then
if ! type -p gpg >/dev/null; then
error "$(gettext "Cannot find the %s binary required for verifying source files.")" "gpg"
ret=1
fi
fi
# openssl - checksum operations
if (( ! SKIPCHECKSUMS )); then
if ! type -p openssl >/dev/null; then
error "$(gettext "Cannot find the %s binary required for validating sourcefile checksums.")" "openssl"
ret=1
fi
fi
# upx - binary compression
if check_option "upx" "y"; then
if ! type -p upx >/dev/null; then
error "$(gettext "Cannot find the %s binary required for compressing binaries.")" "upx"
ret=1
fi
fi
# distcc - compilation with distcc
if check_buildenv "distcc" "y" && ! check_option "distcc" "n"; then
if ! type -p distcc >/dev/null; then
error "$(gettext "Cannot find the %s binary required for distributed compilation.")" "distcc"
ret=1
fi
fi
# ccache - compilation with ccache
if check_buildenv "ccache" "y" && ! check_option "ccache" "n"; then
if ! type -p ccache >/dev/null; then
error "$(gettext "Cannot find the %s binary required for compiler cache usage.")" "ccache"
ret=1
fi
fi
# strip - strip symbols from binaries/libraries
if check_option "strip" "y"; then
if ! type -p strip >/dev/null; then
error "$(gettext "Cannot find the %s binary required for object file stripping.")" "strip"
ret=1
fi
fi
# gzip - compressig man and info pages
if check_option "zipman" "y"; then
if ! type -p gzip >/dev/null; then
error "$(gettext "Cannot find the %s binary required for compressing man and info pages.")" "gzip"
ret=1
fi
fi
return $ret
}
check_build_status() {
if (( ! SPLITPKG )); then
fullver=$(get_full_version)
pkgarch=$(get_pkg_arch)
if [[ -f $PKGDEST/${pkgname}-${fullver}-${pkgarch}${PKGEXT} ]] \
&& ! (( FORCE || SOURCEONLY || NOBUILD )); then
if (( INSTALL )); then
warning "$(gettext "A package has already been built, installing existing package...")"
install_package
exit $?
else
error "$(gettext "A package has already been built. (use %s to overwrite)")" "-f"
exit 1
fi
fi
else
allpkgbuilt=1
somepkgbuilt=0
for pkg in ${pkgname[@]}; do
fullver=$(get_full_version $pkg)
pkgarch=$(get_pkg_arch $pkg)
if [[ -f $PKGDEST/${pkg}-${fullver}-${pkgarch}${PKGEXT} ]]; then
somepkgbuilt=1
else
allpkgbuilt=0
fi
done
if ! (( FORCE || SOURCEONLY || NOBUILD )); then
if (( allpkgbuilt )); then
if (( INSTALL )); then
warning "$(gettext "The package group has already been built, installing existing packages...")"
install_package
exit $?
else
error "$(gettext "The package group has already been built. (use %s to overwrite)")" "-f"
exit 1
fi
fi
if (( somepkgbuilt && ! PKGVERFUNC )); then
error "$(gettext "Part of the package group has already been built. (use %s to overwrite)")" "-f"
exit 1
fi
fi
unset allpkgbuilt somepkgbuilt
fi
}
backup_package_variables() {
local var
for var in ${splitpkg_overrides[@]}; do
local indirect="${var}_backup"
eval "${indirect}=(\"\${$var[@]}\")"
done
}
restore_package_variables() {
local var
for var in ${splitpkg_overrides[@]}; do
local indirect="${var}_backup"
if [[ -n ${!indirect} ]]; then
eval "${var}=(\"\${$indirect[@]}\")"
else
unset ${var}
fi
done
}
run_split_packaging() {
local pkgname_backup=${pkgname[@]}
for pkgname in ${pkgname_backup[@]}; do
pkgdir="$pkgdirbase/$pkgname"
mkdir "$pkgdir"
backup_package_variables
run_package $pkgname
tidy_install
create_package
create_debug_package
restore_package_variables
done
pkgname=${pkgname_backup[@]}
}
# Canonicalize a directory path if it exists
canonicalize_path() {
local path="$1";
if [[ -d $path ]]; then
(
cd_safe "$path"
pwd -P
)
else
printf "%s\n" "$path"
fi
}
dir_is_empty() {
(
shopt -s dotglob nullglob
files=("$1"/*)
(( ${#files} == 0 ))
)
}
m4_include(library/parseopts.sh)
usage() {
printf "makepkg (pacman) %s\n" "$makepkg_version"
echo
printf -- "$(gettext "Make packages compatible for use with pacman")\n"
echo
printf -- "$(gettext "Usage: %s [options]")\n" "$0"
echo
printf -- "$(gettext "Options:")\n"
printf -- "$(gettext " -A, --ignorearch Ignore incomplete %s field in %s")\n" "arch" "$BUILDSCRIPT"
printf -- "$(gettext " -c, --clean Clean up work files after build")\n"
printf -- "$(gettext " -C, --cleanbuild Remove %s dir before building the package")\n" "\$srcdir/"
printf -- "$(gettext " -d, --nodeps Skip all dependency checks")\n"
printf -- "$(gettext " -e, --noextract Do not extract source files (use existing %s dir)")\n" "\$srcdir/"
printf -- "$(gettext " -f, --force Overwrite existing package")\n"
printf -- "$(gettext " -g, --geninteg Generate integrity checks for source files")\n"
printf -- "$(gettext " -h, --help Show this help message and exit")\n"
printf -- "$(gettext " -i, --install Install package after successful build")\n"
printf -- "$(gettext " -L, --log Log package build process")\n"
printf -- "$(gettext " -m, --nocolor Disable colorized output messages")\n"
printf -- "$(gettext " -o, --nobuild Download and extract files only")\n"
printf -- "$(gettext " -p <file> Use an alternate build script (instead of '%s')")\n" "$BUILDSCRIPT"
printf -- "$(gettext " -r, --rmdeps Remove installed dependencies after a successful build")\n"
printf -- "$(gettext " -R, --repackage Repackage contents of the package without rebuilding")\n"
printf -- "$(gettext " -s, --syncdeps Install missing dependencies with %s")\n" "pacman"
printf -- "$(gettext " -S, --source Generate a source-only tarball without downloaded sources")\n"
printf -- "$(gettext " -V, --version Show version information and exit")\n"
printf -- "$(gettext " --allsource Generate a source-only tarball including downloaded sources")\n"
printf -- "$(gettext " --verifysource Download source files (if needed) and perform integrity checks")\n"
printf -- "$(gettext " --asroot Allow %s to run as root user")\n" "makepkg"
printf -- "$(gettext " --check Run the %s function in the %s")\n" "check()" "$BUILDSCRIPT"
printf -- "$(gettext " --config <file> Use an alternate config file (instead of '%s')")\n" "$confdir/makepkg.conf"
printf -- "$(gettext " --holdver Do not update VCS sources")\n"
printf -- "$(gettext " --key <key> Specify a key to use for %s signing instead of the default")\n" "gpg"
printf -- "$(gettext " --nocheck Do not run the %s function in the %s")\n" "check()" "$BUILDSCRIPT"
printf -- "$(gettext " --noprepare Do not run the %s function in the %s")\n" "prepare()" "$BUILDSCRIPT"
printf -- "$(gettext " --nosign Do not create a signature for the package")\n"
printf -- "$(gettext " --pkg <list> Only build listed packages from a split package")\n"
printf -- "$(gettext " --sign Sign the resulting package with %s")\n" "gpg"
printf -- "$(gettext " --skipchecksums Do not verify checksums of the source files")\n"
printf -- "$(gettext " --skipinteg Do not perform any verification checks on source files")\n"
printf -- "$(gettext " --skippgpcheck Do not verify source files with PGP signatures")\n"
echo
printf -- "$(gettext "These options can be passed to %s:")\n" "pacman"
echo
printf -- "$(gettext " --asdeps Install packages as non-explicitly installed")\n"
printf -- "$(gettext " --noconfirm Do not ask for confirmation when resolving dependencies")\n"
printf -- "$(gettext " --needed Do not reinstall the targets that are already up to date")\n"
printf -- "$(gettext " --noprogressbar Do not show a progress bar when downloading files")\n"
echo
printf -- "$(gettext "If %s is not specified, %s will look for '%s'")\n" "-p" "makepkg" "$BUILDSCRIPT"
echo
}
version() {
printf "makepkg (pacman) %s\n" "$makepkg_version"
printf -- "$(gettext "\
Copyright (c) 2006-2014 Pacman Development Team <pacman-dev@archlinux.org>.\n\
Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>.\n\n\
This is free software; see the source for copying conditions.\n\
There is NO WARRANTY, to the extent permitted by law.\n")"
}
# PROGRAM START
# ensure we have a sane umask set
umask 0022
# determine whether we have gettext; make it a no-op if we do not
if ! type -p gettext >/dev/null; then
gettext() {
printf "%s\n" "$@"
}
fi
ARGLIST=("$@")
# Parse Command Line Options.
OPT_SHORT="AcCdefFghiLmop:rRsSV"
OPT_LONG=('allsource' 'asroot' 'check' 'clean' 'cleanbuild' 'config:' 'force' 'geninteg'
'help' 'holdver' 'ignorearch' 'install' 'key:' 'log' 'nobuild' 'nocolor'
'nocheck' 'nodeps' 'noextract' 'noprepare' 'nosign' 'pkg:' 'repackage' 'rmdeps'
'sign' 'skipchecksums' 'skipinteg' 'skippgpcheck' 'source' 'syncdeps'
'verifysource' 'version')
# Pacman Options
OPT_LONG+=('asdeps' 'noconfirm' 'needed' 'noprogressbar')
if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then
exit 1 # E_INVALID_OPTION;
fi
set -- "${OPTRET[@]}"
unset OPT_SHORT OPT_LONG OPTRET
while true; do
case "$1" in
# Pacman Options
--asdeps) ASDEPS=1;;
--noconfirm) PACMAN_OPTS+=" --noconfirm" ;;
--needed) NEEDED=1;;
--noprogressbar) PACMAN_OPTS+=" --noprogressbar" ;;
# Makepkg Options
--allsource) SOURCEONLY=2 ;;
--asroot) ASROOT=1 ;;
-A|--ignorearch) IGNOREARCH=1 ;;
-c|--clean) CLEANUP=1 ;;
-C|--cleanbuild) CLEANBUILD=1 ;;
--check) RUN_CHECK='y' ;;
--config) shift; MAKEPKG_CONF=$1 ;;
-d|--nodeps) NODEPS=1 ;;
-e|--noextract) NOEXTRACT=1 ;;
-f|--force) FORCE=1 ;;
-F) INFAKEROOT=1 ;;
-g|--geninteg) GENINTEG=1 ;;
--holdver) HOLDVER=1 ;;
-i|--install) INSTALL=1 ;;
--key) shift; GPGKEY=$1 ;;
-L|--log) LOGGING=1 ;;
-m|--nocolor) USE_COLOR='n' ;;
--nocheck) RUN_CHECK='n' ;;
--noprepare) RUN_PREPARE='n' ;;
--nosign) SIGNPKG='n' ;;
-o|--nobuild) NOBUILD=1 ;;
-p) shift; BUILDFILE=$1 ;;
--pkg) shift; IFS=, read -ra p <<<"$1"; PKGLIST+=("${p[@]}"); unset p ;;
-r|--rmdeps) RMDEPS=1 ;;
-R|--repackage) REPKG=1 ;;
--skipchecksums) SKIPCHECKSUMS=1 ;;
--skipinteg) SKIPCHECKSUMS=1; SKIPPGPCHECK=1 ;;
--skippgpcheck) SKIPPGPCHECK=1;;
--sign) SIGNPKG='y' ;;
-s|--syncdeps) DEP_BIN=1 ;;
-S|--source) SOURCEONLY=1 ;;
--verifysource) VERIFYSOURCE=1 ;;
-h|--help) usage; exit 0 ;; # E_OK
-V|--version) version; exit 0 ;; # E_OK
--) OPT_IND=0; shift; break 2;;
esac
shift
done
# attempt to consume any extra argv as environment variables. this supports
# overriding (e.g. CC=clang) as well as overriding (e.g. CFLAGS+=' -g').
extra_environment=()
while [[ $1 ]]; do
if [[ $1 = [_[:alpha:]]*([[:alnum:]_])?(+)=* ]]; then
extra_environment+=("$1")
fi
shift
done
# setup signal traps
trap 'clean_up' 0
for signal in TERM HUP QUIT; do
trap "trap_exit $signal \"$(gettext "%s signal caught. Exiting...")\" \"$signal\"" "$signal"
done
trap 'trap_exit INT "$(gettext "Aborted by user! Exiting...")"' INT
trap 'trap_exit USR1 "$(gettext "An unknown error has occurred. Exiting...")"' ERR
# preserve environment variables and canonicalize path
[[ -n ${PKGDEST} ]] && _PKGDEST=$(canonicalize_path ${PKGDEST})
[[ -n ${SRCDEST} ]] && _SRCDEST=$(canonicalize_path ${SRCDEST})
[[ -n ${SRCPKGDEST} ]] && _SRCPKGDEST=$(canonicalize_path ${SRCPKGDEST})
[[ -n ${LOGDEST} ]] && _LOGDEST=$(canonicalize_path ${LOGDEST})
[[ -n ${BUILDDIR} ]] && _BUILDDIR=$(canonicalize_path ${BUILDDIR})
[[ -n ${PKGEXT} ]] && _PKGEXT=${PKGEXT}
[[ -n ${SRCEXT} ]] && _SRCEXT=${SRCEXT}
[[ -n ${GPGKEY} ]] && _GPGKEY=${GPGKEY}
[[ -n ${PACKAGER} ]] && _PACKAGER=${PACKAGER}
[[ -n ${CARCH} ]] && _CARCH=${CARCH}
# default config is makepkg.conf
MAKEPKG_CONF=${MAKEPKG_CONF:-$confdir/makepkg.conf}
# Source the config file; fail if it is not found
if [[ -r $MAKEPKG_CONF ]]; then
source_safe "$MAKEPKG_CONF"
else
error "$(gettext "%s not found.")" "$MAKEPKG_CONF"
plain "$(gettext "Aborting...")"
exit 1 # $E_CONFIG_ERROR
fi
# Source user-specific makepkg.conf overrides, but only if no override config
# file was specified
if [[ $MAKEPKG_CONF = "$confdir/makepkg.conf" && -r ~/.makepkg.conf ]]; then
source_safe ~/.makepkg.conf
fi
# set pacman command if not already defined
PACMAN=${PACMAN:-pacman}
# save full path to command as PATH may change when sourcing /etc/profile
PACMAN_PATH=$(type -P $PACMAN) || true
# check if messages are to be printed using color
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 && ! $USE_COLOR = "n" ]] && check_buildenv "color" "y"; 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[0m"
BOLD="\e[1m"
BLUE="${BOLD}\e[34m"
GREEN="${BOLD}\e[32m"
RED="${BOLD}\e[31m"
YELLOW="${BOLD}\e[33m"
fi
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
# override settings with an environment variable for batch processing
BUILDDIR=${_BUILDDIR:-$BUILDDIR}
BUILDDIR=${BUILDDIR:-$startdir} #default to $startdir if undefined
if [[ ! -d $BUILDDIR ]]; then
if ! mkdir -p "$BUILDDIR"; then
error "$(gettext "You do not have write permission to create packages in %s.")" "$BUILDDIR"
plain "$(gettext "Aborting...")"
exit 1
fi
chmod a-s "$BUILDDIR"
fi
if [[ ! -w $BUILDDIR ]]; then
error "$(gettext "You do not have write permission to create packages in %s.")" "$BUILDDIR"
plain "$(gettext "Aborting...")"
exit 1
fi
# override settings from extra variables on commandline, if any
if (( ${#extra_environment[*]} )); then
export "${extra_environment[@]}"
fi
PKGDEST=${_PKGDEST:-$PKGDEST}
PKGDEST=${PKGDEST:-$startdir} #default to $startdir if undefined
if (( ! (NOBUILD || GENINTEG) )) && [[ ! -w $PKGDEST ]]; then
error "$(gettext "You do not have write permission to store packages in %s.")" "$PKGDEST"
plain "$(gettext "Aborting...")"
exit 1
fi
SRCDEST=${_SRCDEST:-$SRCDEST}
SRCDEST=${SRCDEST:-$startdir} #default to $startdir if undefined
if [[ ! -w $SRCDEST ]] ; then
error "$(gettext "You do not have write permission to store downloads in %s.")" "$SRCDEST"
plain "$(gettext "Aborting...")"
exit 1
fi
SRCPKGDEST=${_SRCPKGDEST:-$SRCPKGDEST}
SRCPKGDEST=${SRCPKGDEST:-$startdir} #default to $startdir if undefined
if (( SOURCEONLY )) && [[ ! -w $SRCPKGDEST ]]; then
error "$(gettext "You do not have write permission to store source tarballs in %s.")" "$SRCPKGDEST"
plain "$(gettext "Aborting...")"
exit 1
fi
LOGDEST=${_LOGDEST:-$LOGDEST}
LOGDEST=${LOGDEST:-$startdir} #default to $startdir if undefined
if (( LOGGING )) && [[ ! -w $LOGDEST ]]; then
error "$(gettext "You do not have write permission to store logs in %s.")" "$LOGDEST"
plain "$(gettext "Aborting...")"
exit 1
fi
PKGEXT=${_PKGEXT:-$PKGEXT}
SRCEXT=${_SRCEXT:-$SRCEXT}
GPGKEY=${_GPGKEY:-$GPGKEY}
PACKAGER=${_PACKAGER:-$PACKAGER}
CARCH=${_CARCH:-$CARCH}
if (( ! INFAKEROOT )); then
if (( EUID == 0 && ! ASROOT )); then
# Warn those who like to live dangerously.
error "$(gettext "Running %s as root is a BAD idea and can cause permanent,\n\
catastrophic damage to your system. If you wish to run as root, please\n\
use the %s option.")" "makepkg" "--asroot"
exit 1 # $E_USER_ABORT
elif (( EUID > 0 && ASROOT )); then
# Warn those who try to use the --asroot option when they are not root
error "$(gettext "The %s option is meant for the root user only. Please\n\
rerun %s without the %s flag.")" "--asroot" "makepkg" "--asroot"
exit 1 # $E_USER_ABORT
elif (( EUID > 0 )) && ! check_buildenv "fakeroot" "y"; then
warning "$(gettext "Running %s as an unprivileged user will result in non-root\n\
ownership of the packaged files. Try using the %s environment by\n\
placing %s in the %s array in %s.")" "makepkg" "fakeroot" "'fakeroot'" "BUILDENV" "$MAKEPKG_CONF"
sleep 1
fi
else
if [[ -z $FAKEROOTKEY ]]; then
error "$(gettext "Do not use the %s option. This option is only for use by %s.")" "'-F'" "makepkg"
exit 1 # TODO: error code
fi
fi
unset pkgname pkgbase pkgver pkgrel epoch pkgdesc url license groups provides
unset md5sums replaces depends conflicts backup source install changelog build
unset makedepends optdepends options noextract
BUILDFILE=${BUILDFILE:-$BUILDSCRIPT}
if [[ ! -f $BUILDFILE ]]; then
error "$(gettext "%s does not exist.")" "$BUILDFILE"
exit 1
else
if [[ $(<"$BUILDFILE") = *$'\r'* ]]; then
error "$(gettext "%s contains %s characters and cannot be sourced.")" "$BUILDFILE" "CRLF"
exit 1
fi
if [[ ${BUILDFILE:0:1} != "/" ]]; then
BUILDFILE="$startdir/$BUILDFILE"
fi
source_safe "$BUILDFILE"
fi
# set defaults if they weren't specified in buildfile
pkgbase=${pkgbase:-${pkgname[0]}}
epoch=${epoch:-0}
if [[ $BUILDDIR = "$startdir" ]]; then
srcdir="$BUILDDIR/src"
pkgdirbase="$BUILDDIR/pkg"
else
srcdir="$BUILDDIR/$pkgbase/src"
pkgdirbase="$BUILDDIR/$pkgbase/pkg"
fi
# set pkgdir to something "sensible" for (not recommended) use during build()
pkgdir="$pkgdirbase/$pkgbase"
if (( GENINTEG )); then
mkdir -p "$srcdir"
chmod a-s "$srcdir"
cd_safe "$srcdir"
download_sources fast
generate_checksums
exit 0 # $E_OK
fi
if have_function pkgver; then
PKGVERFUNC=1
fi
# check the PKGBUILD for some basic requirements
check_sanity || exit 1
# check we have the software required to process the PKGBUILD
check_software || exit 1
if (( ${#pkgname[@]} > 1 )); then
SPLITPKG=1
fi
# test for available PKGBUILD functions
if have_function prepare; then
# "Hide" prepare() function if not going to be run
if [[ $RUN_PREPARE != "n" ]]; then
PREPAREFUNC=1
fi
fi
if have_function build; then
BUILDFUNC=1
fi
if have_function check; then
# "Hide" check() function if not going to be run
if [[ $RUN_CHECK = 'y' ]] || { ! check_buildenv "check" "n" && [[ $RUN_CHECK != "n" ]]; }; then
CHECKFUNC=1
fi
fi
if have_function package; then
PKGFUNC=1
elif [[ $SPLITPKG -eq 0 ]] && have_function package_${pkgname}; then
SPLITPKG=1
fi
if [[ -n "${PKGLIST[@]}" ]]; then
unset pkgname
pkgname=("${PKGLIST[@]}")
fi
# check if gpg signature is to be created and if signing key is valid
if { [[ -z $SIGNPKG ]] && check_buildenv "sign" "y"; } || [[ $SIGNPKG == 'y' ]]; then
SIGNPKG='y'
if ! gpg --list-key ${GPGKEY} &>/dev/null; then
if [[ ! -z $GPGKEY ]]; then
error "$(gettext "The key %s does not exist in your keyring.")" "${GPGKEY}"
else
error "$(gettext "There is no key in your keyring.")"
fi
exit 1
fi
fi
if (( ! PKGVERFUNC )); then
check_build_status
fi
# Run the bare minimum in fakeroot
if (( INFAKEROOT )); then
if (( SOURCEONLY )); then
create_srcpackage
msg "$(gettext "Leaving %s environment.")" "fakeroot"
exit 0 # $E_OK
fi
chmod 755 "$pkgdirbase"
if (( ! SPLITPKG )); then
pkgdir="$pkgdirbase/$pkgname"
mkdir "$pkgdir"
if (( PKGFUNC )); then
run_package
fi
tidy_install
create_package
create_debug_package
else
run_split_packaging
fi
msg "$(gettext "Leaving %s environment.")" "fakeroot"
exit 0 # $E_OK
fi
fullver=$(get_full_version)
msg "$(gettext "Making package: %s")" "$pkgbase $fullver ($(date))"
# if we are creating a source-only package, go no further
if (( SOURCEONLY )); then
if [[ -f $SRCPKGDEST/${pkgbase}-${fullver}${SRCEXT} ]] \
&& (( ! FORCE )); then
error "$(gettext "A source package has already been built. (use %s to overwrite)")" "-f"
exit 1
fi
# Get back to our src directory so we can begin with sources.
mkdir -p "$srcdir"
chmod a-s "$srcdir"
cd_safe "$srcdir"
if (( SOURCEONLY == 2 )); then
download_sources
elif ( (( ! SKIPCHECKSUMS )) || \
( (( ! SKIPPGPCHECK )) && source_has_signatures ) ); then
download_sources fast
fi
check_source_integrity
cd_safe "$startdir"
# if we are root or if fakeroot is not enabled, then we don't use it
if ! check_buildenv "fakeroot" "y" || (( EUID == 0 )); then
create_srcpackage
else
enter_fakeroot
fi
msg "$(gettext "Source package created: %s")" "$pkgbase ($(date))"
exit 0
fi
if (( NODEPS || (NOBUILD && !DEP_BIN ) )); then
# no warning message needed for nobuild
if (( NODEPS )); then
warning "$(gettext "Skipping dependency checks.")"
fi
else
if (( RMDEPS && ! INSTALL )); then
original_pkglist=($(run_pacman -Qq)) # required by remove_dep
fi
deperr=0
msg "$(gettext "Checking runtime dependencies...")"
resolve_deps ${depends[@]} || deperr=1
if (( RMDEPS && INSTALL )); then
original_pkglist=($(run_pacman -Qq)) # required by remove_dep
fi
msg "$(gettext "Checking buildtime dependencies...")"
if (( CHECKFUNC )); then
resolve_deps "${makedepends[@]}" "${checkdepends[@]}" || deperr=1
else
resolve_deps "${makedepends[@]}" || deperr=1
fi
if (( RMDEPS )); then
current_pkglist=($(run_pacman -Qq)) # required by remove_deps
fi
if (( deperr )); then
error "$(gettext "Could not resolve all dependencies.")"
exit 1
fi
fi
# get back to our src directory so we can begin with sources
mkdir -p "$srcdir"
chmod a-s "$srcdir"
cd_safe "$srcdir"
if (( NOEXTRACT && ! VERIFYSOURCE )); then
warning "$(gettext "Using existing %s tree")" "\$srcdir/"
elif (( !REPKG )); then
download_sources
check_source_integrity
(( VERIFYSOURCE )) && exit 0 # $E_OK
if (( CLEANBUILD )); then
msg "$(gettext "Removing existing %s directory...")" "\$srcdir/"
rm -rf "$srcdir"/*
fi
extract_sources
if (( PKGVERFUNC )); then
update_pkgver
check_build_status
fi
if (( PREPAREFUNC )); then
run_prepare
fi
fi
if (( NOBUILD )); then
msg "$(gettext "Sources are ready.")"
exit 0 #E_OK
else
# clean existing pkg directory
if [[ -d $pkgdirbase ]]; then
msg "$(gettext "Removing existing %s directory...")" "\$pkgdir/"
rm -rf "$pkgdirbase"
fi
mkdir -p "$pkgdirbase"
chmod a-srwx "$pkgdirbase"
cd_safe "$startdir"
# if we are root or if fakeroot is not enabled, then we don't use it
if ! check_buildenv "fakeroot" "y" || (( EUID == 0 )); then
if (( ! REPKG )); then
if (( ! ( SPLITPKG || PKGFUNC ) )); then
chmod 755 "$pkgdirbase"
mkdir -p "$pkgdir"
fi
(( BUILDFUNC )) && run_build
(( CHECKFUNC )) && run_check
fi
chmod 755 "$pkgdirbase"
if (( ! SPLITPKG )); then
pkgdir="$pkgdirbase/$pkgname"
mkdir -p "$pkgdir"
if (( PKGFUNC )); then
run_package
fi
tidy_install
create_package
create_debug_package
else
run_split_packaging
fi
else
if (( ! REPKG )); then
(( BUILDFUNC )) && run_build
(( CHECKFUNC )) && run_check
cd_safe "$startdir"
fi
enter_fakeroot
fi
fi
fullver=$(get_full_version)
msg "$(gettext "Finished making: %s")" "$pkgbase $fullver ($(date))"
install_package
exit 0 #E_OK
# vim: set noet: