From 24c2345286b9e155ce3fa1e93bf411ed77caf020 Mon Sep 17 00:00:00 2001 From: Mathieu Audat <mathieu.audat@savoirfairelinux.com> Date: Thu, 17 Nov 2016 16:34:58 -0500 Subject: [PATCH] client-android: add ci cqfd Add continuous integration tool cqfd. This tool uses a docker environment to compile a project within a specific OS and given specific dependencies. Commit used from Github (https://github.com/savoirfairelinux/cqfd): 95847693 Change-Id: I3198389cc65246a2ce2636962da41980c79e2edf Reviewed-by: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com> --- cqfd | 296 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100755 cqfd diff --git a/cqfd b/cqfd new file mode 100755 index 00000000..057d5ddd --- /dev/null +++ b/cqfd @@ -0,0 +1,296 @@ +#!/bin/bash +# +# cqfd - a tool to wrap commands in controlled Docker containers +# +# Copyright (C) 2015-2016 Savoir-faire Linux, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +set -e + +PROGNAME=`basename $0` +dockerfile=".cqfd/docker/Dockerfile" +cqfdrc=".cqfdrc" + +## usage() - print usage on stdout +usage() { + cat <<EOF +Usage: $PROGNAME [OPTION ARGUMENT] [COMMAND] [ARGUMENTS] + +Options: + -f <file> Use file as config file (default .cqfdrc) + -b <flavor_name> Target a specific build flavor. + +Commands: + init Initialize project build container + run Run argument(s) inside build container + release Run argument(s) and release software + help Show this help text + + By default, run is assumed, and the run command is the one + configured in .cqfdrc. + + cqfd is Copyright (C) 2015-2016 Savoir-faire Linux, Inc. + + This program comes with ABSOLUTELY NO WARRANTY. This is free + software, and you are welcome to redistribute it under the terms + of the GNU GPLv3 license; see the LICENSE for more informations. +EOF +} + +# cfg_parser() - parse ini-style config files +# Will parse a ini-style config file, and evaluate it to a bash array. +# Ref: http://theoldschooldevops.com/2008/02/09/bash-ini-parser/ +# arg$1: path to ini file +cfg_parser() { + # bash 4.3 and later break compatibility + if [ $BASH_VERSINFO -ge 4 -a ${BASH_VERSINFO[1]} -gt 2 ]; then + local compat=1 + shopt -s compat42 + fi + + if ! ini="$(<$1)"; then # read the file + die "$1: No such file!" + fi + ini="${ini//[/\[}" # escape [ + ini="${ini//]/\]}" # escape ] + IFS=$'\n' && ini=( ${ini} ) # convert to line-array + ini=( ${ini[*]//;*/} ) # remove comments with ; + ini=( ${ini[*]/\ =/=} ) # remove tabs before = + ini=( ${ini[*]/=\ /=} ) # remove tabs be = + ini=( ${ini[*]/\ =\ /=} ) # remove anything with a space around = + ini=( ${ini[*]/#\\[/\}$'\n'cfg.section.} ) # set section prefix + ini=( ${ini[*]/%\\]/ \(} ) # convert text2function (1) + ini=( ${ini[*]/=/=\( } ) # convert item to array + ini=( ${ini[*]/%/ \)} ) # close array parenthesis + ini=( ${ini[*]/%\\ \)/ \\} ) # the multiline trick + ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2) + ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis + ini[0]="" # remove first element + ini[${#ini[*]} + 1]='}' # add the last brace + if ! eval "$(echo "${ini[*]}")" 2>/dev/null; then # eval the result + die "$1: Invalid ini-file!" + fi + + # restore previous bash behaviour + [ "$compat" = "1" ] && shopt -u compat42 +} + +## die() - exit when an error occured +# $@ messages and variables shown in the error message +die() { + echo "cqfd: fatal: $@" 1>&2 + exit 1 +} + +# docker_build() - Initialize build container +docker_build() { + config_load + if [ ! -f $dockerfile ]; then + die "no Dockerfile found at location $dockerfile" + fi + docker build -q -t "$docker_img_name" `dirname $dockerfile` +} + +# docker_run() - run command in configured container +# A few implementation details: +# +# - The user executing the build commands inside the container is +# named 'builder', with the same uid/gid as your user to keep +# filesystem permissions in sync. +# +# - Your project's source directory is always mapped to ~builder/src/ +# +# - Your ~/.ssh directory is mapped to ~builder/.ssh to provide access +# to the ssh keys (your build may pull authenticated git repos for +# example). +# +# arg$1: the command string to execute as builder +# +docker_run() { + [ -z "$JENKINS_URL" ] && local nojenkins=1 + + # The user may set the CQFD_EXTRA_VOLUMES environment variable + # to map custom volumes inside his development container. + if [ -n "$CQFD_EXTRA_VOLUMES" ]; then + local map extravol + for map in $CQFD_EXTRA_VOLUMES; do + extravol+="-v $map " + done + fi + + if [ -n "$CQFD_EXTRA_HOSTS" ]; then + local map extrahosts + for map in $CQFD_EXTRA_HOSTS; do + extrahosts+="--add-host $map " + done + fi + + # CQFD_EXTRA_ENV is a space-separated list like VAR=value + if [ -n "$CQFD_EXTRA_ENV" ]; then + local map extraenv + for map in $CQFD_EXTRA_ENV; do + extraenv+="-e $map " + done + fi + + docker run --privileged -v "$PWD":/home/builder/src \ + -v ~/.ssh:/home/builder/.ssh \ + --rm \ + $extravol \ + $extrahosts \ + $extraenv \ + ${nojenkins:+ -ti} \ + ${SSH_AUTH_SOCK:+ -v $SSH_AUTH_SOCK:/home/builder/.sockets/ssh} \ + ${SSH_AUTH_SOCK:+ -e SSH_AUTH_SOCK=/home/builder/.sockets/ssh} \ + $docker_img_name \ + /bin/bash -c "groupadd -og $GROUPS -f builders && \ + useradd -s /bin/bash -u $UID -g $GROUPS builder && \ + chown $UID:$GROUPS /home/builder && \ + su builder -p -c \"cd ~builder/src/ && $1\" 2>&1" +} + +# make_archive(): Create a release package. +# Note: the --transform option passed to tar allows to move all the +# specified files at the root of the archive. Therefore, you shouldn't +# include two files with the same name in the list of files to +# archive. +make_archive() { + if [ -z "$release_files" ]; then + die "No files to archive, check files in $cqfdrc" + fi + + for file in $release_files; do + [ -f $file ] || die "Cannot create release: missing $file" + done + + # template the generated archive's filename + local git_short=`git rev-parse --short HEAD 2>/dev/null` + local git_long=`git rev-parse HEAD 2>/dev/null` + local date_rfc3339=`date --rfc-3339='date'` + + # default name for the archive if not set + [ -z "$release_archive" ] && release_archive="%Po-%Pn.tar.xz" + + release_archive=`echo $release_archive | + sed -e 's!%%!%!g; + s!%Gh!'$git_short'!g; + s!%GH!'$git_long'!g; + s!%D3!'$date_rfc3339'!g; + s!%Po!'$project_org'!g; + s!%Pn!'$project_name'!g; + s!%Cf!'$flavor'!g;'` + + # also replace variable names - beware with eval + eval release_archive=`echo $release_archive` + + XZ_OPT=-9 tar --transform "s/.*\///g" -cJf \ + "$release_archive" $release_files +} + +# config_load() - load build settings from cqfdrc +# $1: optional "flavor" of the build, is a suffix of command. +config_load() { + local p_flavor="$1" + + IFS="$IFS" cfg_parser "$cqfdrc" + + cfg.section.project # load the [project] section + project_org="$org" + project_name="$name" + + cfg.section.build # load the [build] section + + # build parameters may be overriden by a flavor defined in the + # build section's 'flavors' parameter. + if [ -n "$p_flavor" ]; then + for flavor in $flavors; do + if [ "$flavor" = "$p_flavor" ]; then + local _found=1 + break + fi + done + + if [ -n "$_found" ]; then + cfg.section."$p_flavor" # load the [$p_flavor] section + else + die "flavor \"$p_flavor\" not found in flavors list" + fi + fi + + build_cmd="$command" + if [ -n "$distro" ]; then + dockerfile=".cqfd/$distro/Dockerfile" + fi + + release_files="`eval echo $files`" + release_archive="$archive" + + # This will look like fooinc_reponame + if [ -n "$project_org" -a -n "$project_name" ]; then + docker_img_name="${project_org}_${project_name}" + else + die "project.org and project.name not configured" + fi +} + +while [ $# -gt 0 ]; do + case "$1" in + help|-h|"--help") + usage + exit 0 + ;; + init) + docker_build + exit $? + ;; + -b) + shift + flavor="$1" + ;; + -f) + shift + cqfdrc="$1" + ;; + run|release) + [ "$1" = "release" ] && make_archive=1 + if [ $# -gt 1 ]; then + shift + build_cmd_alt="$@" + fi + break + ;; + ?*) + die "Unknown command: $1" + ;; + *) + # empty or no argument case + ;; + esac + shift +done + +config_load $flavor + +if [ -n "$build_cmd_alt" ]; then + build_cmd=$build_cmd_alt +elif [ -z "$build_cmd" ]; then + die "No build.command defined in $cqfdrc !" +fi + +docker_run "$build_cmd" + +if [ "$make_archive" = "1" ]; then + make_archive +fi -- GitLab