Commit 1d8581fb authored by Dom Sekotill's avatar Dom Sekotill
Browse files

Added auto-build hook

Added the auto-build hook which is a bit of a beast. It lacks
documentation at the moment so here is a git config reminder with
defaults:

[auto-build]
	location = /tmp/build/
	max-build = 4
[auto-build "refs/...[*]"]
	destination = /tmp
	backup = yes
	profile = <auto detected>
	command = <from profile>
	pattern = <from profile>
parent 585c54c5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,5 +17,6 @@


nobase_dist_pkgdata_SCRIPTS = \
	post-receive/auto-build \
	update/ref-locks \
	update/whitespace-errors
+267 −0
Original line number Diff line number Diff line
#! /bin/bash

set -e

config ()
{
	git config --get auto-build`printf ".%s" "$@"`
}

profile ()
{
	local build pattern
	case "$1" in
		autotools)
			build='autoreconf --install && configure && make dist'
			pattern='$(distdir).{tar{,.*},zip}'
			;;
		python)
			build='python setup.py bdist'
			pattern='dist/*.{tar{,.*},zip}'
			;;
		autobuild)
			build='_autobuild_build'
			pattern='_autobuild_pattern'
			;;
	esac
	case "$2" in
		build) echo "$build" ;;
		pattern) echo "$pattern" ;;
		*) return 1 ;;
	esac
}

determine_profile ()
{
	[ "$1" ] && cd "$1"
	local file flag name found
	local FILES="\
		c|configure.ac \
		m|Makefile.am \
		p|setup.py"

	for file in $FILES; do
		flag=`echo "$file" | cut -f1 -d\|`
		name=`echo "$file" | cut -f2 -d\|`
		[ -e "$name" ] && found="$found$flag"
	done

	case "$found" in
		*cm*) echo autotools ;;
		*p*)  echo distutils ;;
		*)    echo autobuild ;;
	esac
}

running ()
{
	if [ -d /proc ]; then
		[ -e /proc/$1 ] || return 1
	elif type ps; then
		ps $1 || return 1
	else
		return 1
	fi
	return 0
} >/dev/null 2>/dev/null

is_git_workspace ()
{
	git --git-dir="$1/.git" rev-parse 2>/dev/null
}

_normalise_ref ()
{
	local obj
	IFS=$'\n' obj=( `git rev-parse --symbolic-full-name --glob="*$1*"` ) \
		|| return $?
	[ "$obj" ] || obj="$1"
	echo "${obj#refs/}"
}

_get_config_section ()
{
	local fragments sections num=0 path sec size
	IFS=$'/' fragments=( $1 )
	IFS=$'\n' sections=( `git config --list \
		| grep -e '^auto-build' \
		| sed -e 's/=.*//' -e 's/\.[^.]*$//' -e 's/^auto-build\.\?//' \
		| sort -u
		` )
	while [ ${#sections[@]} -gt 1 ]; do
		path=`
			printf "${fragments[0]}"
			[ "${fragments[1]}" ] && printf "/%s" "${fragments[@]:1:num}" `
		num=$((num + 1))
		size=${#sections[@]}
		IFS=$'\n' sections=( `
			for sec in "${sections[@]}"; do
				case "$path" in
					$sec) echo "$sec"; continue ;;
				esac
				case "$sec" in
					$path*) echo "$sec" ;;
				esac
			done | sort -r
		` )
		[ $size -eq ${#sections[@]} ] && [ $num -ge ${#fragments[@]} ] && break
	done
	echo "$sections"
	[ ${#sections[@]} -gt 0 ]
}

_make_variable_replace ()
{
	local rep_str="${1//\'/\'\\\'\'}" # escape single quotes ' -> '\''
	local target='__auto-build_get-variable__'
	local makecode=".PHONY:$target\n$target:\n\t@echo '$rep_str'\n"
	printf "$makecode" | make -f Makefile -f- "$target"
}

_find_dist ()
{
	local pattern=`_make_variable_replace "$1"`
	[ "$pattern" ] && eval ls "$pattern" 2>/dev/null || true
}

_acquired_lock ()
{
	mkdir -p `dirname "$1"`
	ln -s $$ "$1" || [ `readlink "$1"` -eq $$ ]
} 2>/dev/null

_clear_stale_lock ()
{
	local lock="$1" lockpid running

	lockpid=`readlink "$lock"` || return 0
	[ $lockpid -eq $$ ] && return 0

	_acquired_lock "$lock.$lockpid" || return 1
	if ! running $lockpid; then
		rm "$lock"
		rm "$lock.$lockpid"
		return 0
	else
		rm "$lock.$lockpid"
		return 1
	fi
}

_backup ()
{
	# recursively move $1->$1.01, $1.01->$1.02, etc...
	local base="$1" cur="${2-$1}" idx="${3-1}"
	local nxt=`printf "%s.%02i" "$base" "$idx"`
	[ -e "$cur" ] || return 0
	_backup "$base" "$nxt" $((idx + 1))
	mv "$cur" "$nxt"
}

build ()
{
	local ref=`_normalise_ref "$1"`
	local confsec=`_get_config_section "$ref"`
	local dbuild=`config location || echo /tmp/build/`
	local dmax=`config max-builds || echo 4`
	local dest=`config "$confsec" destination || echo /tmp`
	local profile cmd pattern
	local i dir lock files file

	dest=`readlink -f "$dest"`

	# make sure we start in $GIT_DIR
	if [ "$GIT_DIR" ]; then
		cd "$GIT_DIR"
		export GIT_DIR=`readlink -f "$GIT_DIR"`
	fi

	# lock the build dir
	while true; do
		for i in `seq 1 $dmax`; do
			dir="$dbuild$i"
			lock="$dir.lock"
			until _acquired_lock "$lock"; do
				_clear_stale_lock "$lock" || break
			done
			_acquired_lock "$lock" && break
		done
		_acquired_lock "$lock" && break
		sleep 4
	done
	trap "[ -L '$lock' ] && rm '$lock'" EXIT

	# create the build dir if it does not exist
	if ! [ -e "$dir" ]; then
		mkdir -p "$dir"
	elif ! [ -d "$dir" ]; then
		echo "the build directory $dir exists but is not a directory" >&2
		return 1
	fi

	# create the git repository if it does not exist
	is_git_workspace "$dir" || git clone "$GIT_DIR" "$dir" >&2

	# switch to build dir, clean it out & get the correct commit
	cd "$dir"
	(
		unset GIT_DIR
		git clean -f -d -q -x
		git reset --hard
		git fetch origin
		git checkout "${ref/heads/remotes/origin}"
	) >&2

	# get the settings for this build
	profile=`config "$confsec" profile || determine_profile`
	cmd=`config "$confsec" command || profile "$profile" build`
	pattern=`config "$confsec" pattern || profile "$profile" pattern`
	backup=`config "$confsec" backup | tr '[:upper:]' '[:lower:]' || echo yes`

	# run the build command
	if ! eval "$cmd" >&2; then
		echo "command failed: $cmd" >&2
		return 1
	fi

	# find the generated files
	if ! IFS=$'\n' files=( `_find_dist "$pattern"` ); then
		echo "failed to find files with pattern:" >&2
		_make_variable_replace "$pattern" >&2
		return 1
	fi

	# move the files to $dest
	mkdir -p "$dest"
	for file_src in "${files[@]}"; do
		file_dst="$dest/`basename "$file_src"`"
		case "$backup" in
			y|yes|true) _backup "$file_dst" ;;
		esac
		mv "$file_src" "$file_dst"
		echo "$file_dst"
	done
}

main ()
{
	local old new ref
	while read old new ref; do
		if [ "$ref" ]; then
			log=`printf $ref | cksum | cut -f1 -d\ `
			log="/tmp/build.$log.log"
			build "$ref" </dev/null &>"$log" &
		fi
	done
}

if [ "$0" -ef "$BASH_SOURCE" ]; then
	case $# in
		0) main ;;
		1) build "$1" ;;
		*)
			echo "at most one argument may be given"
			exit 1
			;;
	esac
fi