diff --git a/.gitignore b/.gitignore index 4463bf0..6d68533 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ packages *.log *~ +__pycache__ +*.pyc +*.changes +*.deb diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3a4edb2 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +.PHONY: test +test: + python3 -m unittest discover "./test" "*_test.py" diff --git a/README.md b/README.md index fd502a3..b27257c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,34 @@ # Tamarin -Usine à paquets binaires Debian basée sur Docker. +Usine à paquets GNU/Linux + +## Statut + +Expérimental + +## Formats de paquets/distributions supportés + +- `.deb` (Debian, Ubuntu) ## Dépendances -Vous devez avoir [docker](https://docs.docker.com/installation/) installé sur votre machine. +- [Python 3](https://www.python.org/downloads/) +- [Docker](>= 17.03) -## Utilisation +## Usage +```bash +./package --help ``` -Usage: ./package.sh -p project_path [-d destination] [-i image] [-k] +## Fonctionnement -Parameters: +### Les profils de construction - -p Path to the project to build - -a Optional : Architecture choice (-a i386 for example) - -d Optional : Destination of the builed packages (default ./packages) - -i Optional : Name of the Docker image to use for build (default: debian:jessie) - -k Optional : Keep the Docker container after build - -b Optional : Build directory (default /tmp) - -B Optional : Build branch (for git projects only) (default dist/ubuntu/precise/master) -``` +TODO + +### Les attributs de construction + +TODO ## Licence diff --git a/check-install b/check-install deleted file mode 100755 index 6364a11..0000000 --- a/check-install +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash - -DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source "$DIR/lib/util.sh" - -function show_usage { - echo - echo "Usage: $0 deb_file [image]" - echo - echo "Paramètres: " - echo - echo " - deb_file Chemin vers le paquet Debian dont on doit vérifier l'installation" - echo " - image Optionnel - Nom de l'image Docker à utiliser comme environnement pour tester l'installation. Défaut: debian:jessie" - echo -} - -function create_container { - - # Escape image name - local escaped_basename=$(echo "$BASE_IMAGE" | sed 's/[^a-z0-9\-\_\.]/\_/gi') - # Generate container tag - container_tag="tamarin:${escaped_basename}_$(date +%s)" - # Create temporary dir for the Dockerfile - local temp_dir="$(mktemp -d)" - - # Link lib folder - ln -s $(readlink -f "$DIR/lib") "$temp_dir/lib" - - # Create Dockerfile - cat << EOF > "$temp_dir/Dockerfile" - FROM $BASE_IMAGE - - ENV DEBIAN_FRONTEND noninteractive - - RUN apt-get update && apt-get install --yes gdebi-core - - ADD ./lib /root/.tamarin - RUN chmod +x /root/.tamarin/install.sh - - VOLUME /deb - - ENTRYPOINT ["/root/.tamarin/install.sh"] - -EOF - - # Build image - tar -C "$temp_dir" -czh . | docker build -t "$container_tag" - 2> >(error) 1> >(info) - - # Delete temporary folder - rm -rf "$temp_dir" - -} - -function main { - - # Create container image - create_container - - # Run container and install package - docker run -e "DISTRIB=$BASE_IMAGE" --rm -v="$DEB_DIR:/deb" "$container_tag" "/deb/$DEB_NAME" - - # Check for return - if [ $? != 0 ]; then - fatal "Installation did not complete correctly !" - fi - - info "Installation complete." - -} - -# Test for arguments -if [ -z "$1" ]; then - show_usage - exit 1 -fi - -DEB_PATH=$(readlink -f "$1") -DEB_NAME=$(basename "$DEB_PATH") -DEB_DIR=$(dirname "$DEB_PATH") -BASE_IMAGE="${2:-debian:jessie}" - -main diff --git a/hooks/build/debian/build b/hooks/build/debian/build new file mode 100755 index 0000000..1f47226 --- /dev/null +++ b/hooks/build/debian/build @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd src +dpkg-buildpackage -b -a"${TAMARIN_TARGET_ARCH}" diff --git a/hooks/containerbuild/debian/install-build-essential b/hooks/containerbuild/debian/install-build-essential new file mode 100755 index 0000000..1db0c28 --- /dev/null +++ b/hooks/containerbuild/debian/install-build-essential @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +echo 'ENV DEBIAN_FRONTEND=noninteractive' >> Dockerfile +echo 'RUN apt-get update && apt-get install --yes --no-install-recommends build-essential devscripts equivs python3' >> Dockerfile +echo 'ENV DEBIAN_FRONTEND=' >> Dockerfile diff --git a/hooks/containerbuild/debian/install-git b/hooks/containerbuild/debian/install-git new file mode 100755 index 0000000..12a9d17 --- /dev/null +++ b/hooks/containerbuild/debian/install-git @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +echo 'ENV DEBIAN_FRONTEND=noninteractive' >> Dockerfile +echo 'RUN apt-get update && apt-get install --yes --no-install-recommends git-core' >> Dockerfile +echo 'ENV DEBIAN_FRONTEND=' >> Dockerfile diff --git a/hooks/install-git-containerbuild b/hooks/install-git-containerbuild deleted file mode 100755 index b1f91ce..0000000 --- a/hooks/install-git-containerbuild +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -echo "RUN apt-get update && apt-get install --yes --no-install-recommends git-core" >> Dockerfile diff --git a/hooks/99-export-dist-postbuild b/hooks/postbuild/debian/export-dist-postbuild similarity index 58% rename from hooks/99-export-dist-postbuild rename to hooks/postbuild/debian/export-dist-postbuild index 923ef78..b65aa2e 100755 --- a/hooks/99-export-dist-postbuild +++ b/hooks/postbuild/debian/export-dist-postbuild @@ -1,9 +1,7 @@ #!/bin/bash -source "${TAMARIN_UTIL}" - function move_output_to_dist { - find '../' -maxdepth 1 -name "$1" -type f -print0 | xargs -0r mv -t /dist/ + find . -maxdepth 1 -name "$1" -type f -print0 | xargs -0r mv -t /dist/ } # Create new directory @@ -14,6 +12,3 @@ move_output_to_dist "*.deb" move_output_to_dist "*.changes" move_output_to_dist "*.dsc" move_output_to_dist "*.tar.{bz2,gz,lzma,xz}" - -# Configure files permissions -chown -R ${HOST_UID}:${HOST_GID} /dist diff --git a/hooks/07-add-package-version-suffix-prebuild b/hooks/prebuild/debian/add-package-version-suffix similarity index 52% rename from hooks/07-add-package-version-suffix-prebuild rename to hooks/prebuild/debian/add-package-version-suffix index 7f29d21..f2b05c5 100755 --- a/hooks/07-add-package-version-suffix-prebuild +++ b/hooks/prebuild/debian/add-package-version-suffix @@ -1,23 +1,24 @@ #!/usr/bin/env bash -source "${TAMARIN_UTIL}" + +cd src if [ ! -f debian/changelog ]; then - info "No changelog. Skipping..." + tamarin_info "No changelog. Skipping..." exit fi -if [ $(get_opt no_version_suffix 'no') == 'yes' ]; then - info "Not adding version suffix." +if [ $(tamarin_db get no_version_suffix 'no') == 'yes' ]; then + tamarin_info "Not adding version suffix." exit fi if [ -d .git ]; then - info "It seems to be a Git repository. Generating version suffix based on Git history..." + tamarin_info "It seems to be a Git repository. Generating version suffix based on Git history..." commit_count=$(git rev-list --count HEAD) current_commit=$(git log -n 1 --pretty=format:"%h") version_suffix=tamarin${commit_count}~${current_commit} else - info "Not a Git project. Fallback to timestamp for suffix generation..." + tamarin_info "Not a Git project. Fallback to timestamp for suffix generation..." version_suffix=tamarin$(date +%Y%m%d%H%M) fi diff --git a/hooks/prebuild/debian/complete-project-db b/hooks/prebuild/debian/complete-project-db new file mode 100755 index 0000000..c86ef37 --- /dev/null +++ b/hooks/prebuild/debian/complete-project-db @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +cd src + +if [ -z "$(tamarin_db get project_name)" ]; then + if [ ! -d ".git" ]; then + tamarin_error "This project is not managed with Git ! Cannot extract the project's name without it !" + exit 1 + fi + project_name=$(basename $(git config --get remote.origin.url) | sed 's/.git$//' | tr '[:upper:]' '[:lower:]') + tamarin_info "Extracted project name from Git metadata: \"${project_name}\"" + tamarin_db set project_name "${project_name}" +fi diff --git a/hooks/prebuild/debian/copy-sources-to-workspace b/hooks/prebuild/debian/copy-sources-to-workspace new file mode 100755 index 0000000..6324c31 --- /dev/null +++ b/hooks/prebuild/debian/copy-sources-to-workspace @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +tamarin_info "Copying sources to workspace 'src' directory..." + +mkdir src +# Copying all read-only sources to the current workspace src +cp -r /src/. ./src diff --git a/hooks/05-create-changelog-prebuild b/hooks/prebuild/debian/create-changelog similarity index 64% rename from hooks/05-create-changelog-prebuild rename to hooks/prebuild/debian/create-changelog index a035de7..4764c9c 100755 --- a/hooks/05-create-changelog-prebuild +++ b/hooks/prebuild/debian/create-changelog @@ -1,16 +1,17 @@ #!/usr/bin/env bash -source "${TAMARIN_UTIL}" + +cd src if [ -f debian/changelog ] || [ ! -d .git ]; then - info "Not a Git repository or Debian changelog already exists !" + tamarin_info "Not a Git repository or Debian changelog already exists !" exit fi # Get commits log as changelog -BUILD_TAG=$(get_opt build_tag "last") +BUILD_TAG=$(tamarin_db get build_tag "last") -echo "BUILD TAG IS ${BUILD_TAG}" +tamarin_debug "BUILD TAG IS ${BUILD_TAG}" if [[ ${BUILD_TAG} == "last" ]] then @@ -18,13 +19,13 @@ then else tagbranch="build-tag-${BUILD_TAG}" git checkout -b ${tagbranch} - set_opt "tag_branch" "${tag_branch}" + tamarin_db set "tag_branch" "${tag_branch}" local tags="${BUILD_TAG}" fi if [[ -z ${tags} ]] then - warn "No release tag found, you repo must have a tag like 'release/X.X'" + tamarin_warn "No release tag found, you repo must have a tag like 'release/X.X'" exit fi @@ -36,21 +37,21 @@ do # Set the top commiter as the maintainer of the project if not defined top_contributor=$(git log --pretty=short | git shortlog -s -n -e | sed 's/^\s*[0-9]*\s*//g' | head -n 1) - maintainer=$(get_opt maintainer "${top_contributor}") + maintainer=$(tamarin_db get maintainer "${top_contributor}") - project_name=$(get_opt project_name) + project_name=$(tamarin_db get project_name) version=${tag#*/} #$(get_opt version 0.0.0) version=${version/_/-} #$(get_opt version 0.0.0) - distribution=$(get_opt distribution UNRELEASED) - urgency=$(get_opt urgency low) + distribution=$(tamarin_db get distribution UNRELEASED) + urgency=$(tamarin_db get urgency low) package_version=${version} - # Define project_version opt if not defined - if [ -z "$(get_opt project_version)" ]; then + # Define project_version if not defined + if [ -z "$(tamarin_db get project_version)" ]; then # Share computed project version - set_opt project_version "${version}" + tamarin_db set project_version "${version}" fi echo "${project_name} (${version}) ${distribution}; urgency=${urgency}" >> debian/changelog diff --git a/hooks/06-create-dummy-changelog-prebuild b/hooks/prebuild/debian/create-dummy-changelog similarity index 74% rename from hooks/06-create-dummy-changelog-prebuild rename to hooks/prebuild/debian/create-dummy-changelog index 288e586..9b97488 100755 --- a/hooks/06-create-dummy-changelog-prebuild +++ b/hooks/prebuild/debian/create-dummy-changelog @@ -1,14 +1,15 @@ #!/usr/bin/env bash -source "${TAMARIN_UTIL}" + +cd src if [ -f debian/changelog ] || [ ! -d .git ]; then - info "Not a Git repository or Debian changelog already exists !" + tamarin_info "Not a Git repository or Debian changelog already exists !" exit fi changelog="debian/changelog" -project_name=$(get_opt project_name) -project_version=$(get_opt project_version 0.0.0) +project_name=$(tamarin_db get project_name) +project_version=$(tamarin_db get project_version 0.0.0) date=$(date -R) top_contributor=$(git log --pretty=short | git shortlog -s -n -e | sed 's/^\s*[0-9]*\s*//g' | head -n 1) current_commit=$(git log -n 1 --pretty=format:%h) diff --git a/hooks/10-install-build-depends-prebuild b/hooks/prebuild/debian/install-build-depends similarity index 96% rename from hooks/10-install-build-depends-prebuild rename to hooks/prebuild/debian/install-build-depends index e63e432..0ff2014 100755 --- a/hooks/10-install-build-depends-prebuild +++ b/hooks/prebuild/debian/install-build-depends @@ -1,5 +1,7 @@ #!/bin/bash +cd src + if [ -f debian/control ]; then echo "Installing build dependencies..." apt-get update diff --git a/src-example/hello-world b/hooks/prebuild/debian/load-project-db similarity index 56% rename from src-example/hello-world rename to hooks/prebuild/debian/load-project-db index 124f6f6..0f7d99b 100755 --- a/src-example/hello-world +++ b/hooks/prebuild/debian/load-project-db @@ -1,3 +1,3 @@ #!/usr/bin/env bash -echo $1 +tamarin_db load diff --git a/lib/build.py b/lib/build.py new file mode 100644 index 0000000..025012c --- /dev/null +++ b/lib/build.py @@ -0,0 +1,31 @@ +import sys, os, argparse, tempfile +sys.path.append(os.path.dirname(__file__) + '/lib') +import tamarin + +def get_args_parser(): + parser = argparse.ArgumentParser(description="Tamarin's container entrypoint") + # Define available/required arguments and flags + parser.add_argument("profile", help="The selected profile to use for the build") + parser.add_argument("arch", help="The selected profile to use for the build") + return parser + +def get_buildtools_dir(): + return os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + "/buildtools") + +if __name__ == '__main__': + + parser = get_args_parser() + args = parser.parse_args() + + profile = tamarin.load_profile(args.profile) + + workspace = tempfile.mkdtemp(prefix="tamarin_") + + env = os.environ.copy() + env['TAMARIN_TARGET_ARCH'] = args.arch + env['TAMARIN_WORKSPACE'] = workspace + env['PATH'] = env['PATH'] + ':' + get_buildtools_dir() + + tamarin.run_profile_hooks(profile, 'prebuild', cwd=workspace, env=env) + tamarin.run_profile_hooks(profile, 'build', cwd=workspace, env=env) + tamarin.run_profile_hooks(profile, 'postbuild', cwd=workspace, env=env) diff --git a/lib/build.sh b/lib/build.sh deleted file mode 100755 index b7bb8a2..0000000 --- a/lib/build.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -LIB_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -export TAMARIN_UTIL="${LIB_DIR}/util.sh" - -source "${TAMARIN_UTIL}" - -DIST_DIR="${BASE_DIR}/dist" -SRC_DIR="${BASE_DIR}/src" -PROJECT_NAME=${1} -BUILD_BRANCH=${2} -BUILD_TAG=${4} - -function build_project() -{ - - info "Building project '${PROJECT_NAME}' for ${TARGET_ARCH} architecture..." - - # Initalize opts - set_opt project_name "${PROJECT_NAME}" - set_opt build_branch "${BUILD_BRANCH}" - set_opt build_tag "${BUILD_TAG}" - - local workspace=$(mktemp -d)/${PROJECT_NAME} - info "Build dir is ${workspace}" - mkdir -p "${workspace}" - - # Copy sources to workspace - cd ${SRC_DIR} - cp -r ${SRC_DIR}/. "${workspace}" - - cd "$workspace" - - load_local_opts - exec_hooks "prebuild" "${workspace}" - - dpkg-buildpackage -b -a"${TARGET_ARCH}" 2> >(stderr) 1> >(stdout) - - if [ $? != 0 ]; then - fatal "The build process has not completed successfuly !" - fi - - exec_hooks "postbuild" "${workspace}" - -} - -build_project diff --git a/lib/buildtools/tamarin_db b/lib/buildtools/tamarin_db new file mode 100755 index 0000000..3446635 --- /dev/null +++ b/lib/buildtools/tamarin_db @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +PROJECT_DB_FILE="/src/.tamarinrc" +GLOBAL_DB_FILE="${TAMARIN_WORKSPACE}/.tamarinrc" +KEY_PREFIX="tamarin_db_" + +function tamarin_load_project_db { + if [ -e "${PROJECT_DB_FILE}" ]; then + tamarin_info "Loading project database..." + while read line; do + if [[ ! "${line}" =~ ^\s*# ]]; then + set -- $(echo $line | tr '=' ' ') + local key=$1 + local value=$2 + tamarin_debug "Load $key=$value" + tamarin_db_set $key $value + fi + done < "${PROJECT_DB_FILE}" + fi +} + +function tamarin_db_get { + local opt_name=${KEY_PREFIX}${1} + local default_value=${2} + touch "${GLOBAL_DB_FILE}" + source "${GLOBAL_DB_FILE}" + echo ${!opt_name:-${default_value}} +} + +function tamarin_db_set { + local opt_name=${1} + local opt_value=${2} + mkdir -p "$(dirname ${GLOBAL_DB_FILE})" + touch "${GLOBAL_DB_FILE}" + sed -i "s/^${KEY_PREFIX}${opt_name}*$//" "${GLOBAL_DB_FILE}" + echo "local ${KEY_PREFIX}${opt_name}=\"${opt_value}\"" >> "${GLOBAL_DB_FILE}" +} + +case $1 in + set) + tamarin_db_set ${@:2} + ;; + get) + tamarin_db_get ${@:2} + ;; + load) + tamarin_load_project_db + ;; + *) + tamarin_error "Invalid action '$1'. Must be get, set or load !" + exit 1 + ;; +esac diff --git a/lib/buildtools/tamarin_debug b/lib/buildtools/tamarin_debug new file mode 100755 index 0000000..4266a19 --- /dev/null +++ b/lib/buildtools/tamarin_debug @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$@" ]; then + while read str; do + tamarin_log DEBUG "${str}" + done +else + tamarin_log DEBUG "$@" +fi diff --git a/lib/buildtools/tamarin_error b/lib/buildtools/tamarin_error new file mode 100755 index 0000000..9be8c07 --- /dev/null +++ b/lib/buildtools/tamarin_error @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$@" ]; then + while read str; do + tamarin_log ERROR "${str}" + done +else + tamarin_log ERROR "$@" +fi diff --git a/lib/buildtools/tamarin_info b/lib/buildtools/tamarin_info new file mode 100755 index 0000000..518aa98 --- /dev/null +++ b/lib/buildtools/tamarin_info @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$@" ]; then + while read str; do + tamarin_log INFO "${str}" + done +else + tamarin_log INFO "$@" +fi diff --git a/lib/buildtools/tamarin_log b/lib/buildtools/tamarin_log new file mode 100755 index 0000000..b8c6072 --- /dev/null +++ b/lib/buildtools/tamarin_log @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Colors + +COLOR_INFO='\e[0;36m' +COLOR_ERROR='\e[0;31m' +COLOR_WARN='\e[0;33m' +COLOR_SUCCESS='\e[0;32m' +COLOR_DEBUG='\e[0;35m' + +function log { + local args=( $@ ) + local color=COLOR_${args[0]} + echo -e "${!color}[${args[0]}] $(remove_ansi ${args[@]:1})\e[0m" +} + +function remove_ansi { + echo "$@" | sed 's,\x1B\[[0-9;]*[a-zA-Z],,g' +} + +if [ $# -ne 0 ]; then + log $@ +fi diff --git a/lib/buildtools/tamarin_success b/lib/buildtools/tamarin_success new file mode 100755 index 0000000..4484f97 --- /dev/null +++ b/lib/buildtools/tamarin_success @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$@" ]; then + while read str; do + tamarin_log SUCCESS "${str}" + done +else + tamarin_log SUCCESS "$@" +fi diff --git a/lib/buildtools/tamarin_warn b/lib/buildtools/tamarin_warn new file mode 100755 index 0000000..4935b7e --- /dev/null +++ b/lib/buildtools/tamarin_warn @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$@" ]; then + while read str; do + tamarin_log WARN "${str}" + done +else + tamarin_log WARN "$@" +fi diff --git a/lib/install.sh b/lib/install.sh deleted file mode 100644 index 78a5112..0000000 --- a/lib/install.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e - -DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source "${DIR}/util.sh" - -info "Updating packages definition..." -apt-get update 2> >(stderr) 1> >(stdout) - -info "Installing package $1..." -gdebi --n "$1" 2> >(stderr) 1> >(stdout) diff --git a/lib/tamarin.py b/lib/tamarin.py new file mode 100644 index 0000000..de1bfb8 --- /dev/null +++ b/lib/tamarin.py @@ -0,0 +1,65 @@ +import os, glob, subprocess, configparser, codecs, sys + +def run_profile_hooks(profile, step, **kwargs): + hooks_dir = get_hooks_dir() + step_hooks = profile[step]["hooks"] + if not step_hooks: + return + for hook_name in step_hooks.split(","): + trimmed_hook_name = hook_name.strip(' \t\n\r') + if not trimmed_hook_name: + continue + hook_path = os.path.join(hooks_dir, trimmed_hook_name) + run([hook_path], **kwargs) + +def get_hooks_dir(): + return os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + "/../hooks") + +def get_lib_dir(): + return os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + "/../lib") + +def load_profile(profile_name, debug=False): + profile_filename = profile_name+".conf" + for profile_file in get_available_profiles(): + if profile_filename == os.path.basename(profile_file): + config = configparser.ConfigParser() + with codecs.open(profile_file, encoding = 'utf-8', mode = 'r') as handle: + config.read_file(handle) + return config + return None + +def get_profiles_dir(): + return os.path.realpath(os.path.dirname(os.path.abspath(__file__)) + "/../profiles") + +def get_available_profiles(): + return glob.glob(get_profiles_dir() + '/*.conf') + +def get_available_profile_names(): + profile_files = get_available_profiles() + return [os.path.splitext(os.path.basename(f))[0] for f in profile_files] + +def get_workspace_dir(): + """Return the absolute path to the tamarin workspace ($HOME/.tamarin)""" + home = os.environ["HOME"] + return os.path.join(os.sep, home, '.tamarin') + +def get_workspace_subdir(subdir): + """Return the absolute path to a subdirectory in tamarin workspace""" + dir_path = os.path.join(os.sep, get_workspace_dir(), subdir) + os.makedirs(dir_path, exist_ok=True) + return dir_path + +def run(cmd, captureOutput=False, pty=False, debug=False, **kwargs): + """Execute an arbitrary command on the system""" + if debug: + print(" ".join(cmd)) + stdin=subprocess.PIPE + if pty: + kwargs['stdin'] = sys.stdin + if captureOutput: + return subprocess.check_output(cmd, **kwargs) + else: + return subprocess.check_call(cmd, **kwargs) + +def run_docker(args, captureOutput=False, **kwargs): + return run(["docker"] + args, captureOutput=captureOutput, **kwargs) diff --git a/lib/util.sh b/lib/util.sh deleted file mode 100644 index e0f0857..0000000 --- a/lib/util.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env bash - -HOOKS_DIR="${BASE_DIR}/hooks" -DEFAULT_OPTS_FILE="${BASE_DIR}/tmp/default_opts" -OPT_FILE="${BASE_DIR}/tmp/tamarin/opts" -OPT_PREFIX="tamarin_opt_" -LOCAL_OPTS_FILE=".tamarinrc" - -# Colors - -COLOR_INFO='\e[0;36m' -COLOR_FATAL='\e[0;31m' -COLOR_WARN='\e[0;33m' -COLOR_SUCCESS='\e[0;32m' -COLOR_ERR='\e[0;37m' -COLOR_OUT='\e[0;37m' -COLOR_DEBUG='\e[0;35m' - -function stderr { - if [ -z "$@" ]; then - while read str; do - log ERR "${str}" - done - else - log stderr "$@" - fi -} - -function stdout { - if [ -z "$@" ]; then - while read str; do - log OUT "${str}" - done - else - log OUT "$@" - fi -} - -function info { - if [ -z "$@" ]; then - while read str; do - log INFO "${str}" - done - else - log INFO "$@" - fi -} - -function warn { - if [ -z "$@" ]; then - while read str; do - log WARN "${str}" - done - else - log WARN "$@" - fi -} - -function debug { - if [ -z "$@" ]; then - while read str; do - log DEBUG "${str}" - done - else - log DEBUG "$@" - fi -} - -function fatal { - if [ -z "$@" ]; then - while read str; do - log FATAL "${str}" >&2 - done - else - log FATAL "$@" >&2 - fi - exit 1 -} - -function success { - if [ -z "$@" ]; then - while read str; do - log SUCCESS "${str}" - done - else - log SUCCESS "$@" - fi -} - -function log { - local args=( $@ ) - local color=COLOR_${args[0]} - echo -e "${!color}[${args[0]}] $(remove_ansi ${args[@]:1})\e[0m" -} - -function remove_ansi { - echo "$@" | sed 's,\x1B\[[0-9;]*[a-zA-Z],,g' -} - -function load_local_opts { - if [ -e "${LOCAL_OPTS_FILE}" ]; then - info "Loading local opts..." - while read line; do - if [[ ! "${line}" =~ ^\s*# ]]; then - set -- $(echo $line | tr '=' ' ') - local key=$1 - local value=$2 - debug "Load opt $key=$value" - set_opt $key $value - fi - done < "${LOCAL_OPTS_FILE}" - fi -} - -function get_opt { - local opt_name=${OPT_PREFIX}${1} - local default_value=${2} - touch "${OPT_FILE}" - source "${OPT_FILE}" - echo ${!opt_name:-${default_value}} -} - -function set_opt { - local opt_name=${1} - local opt_value=${2} - mkdir -p "$(dirname ${OPT_FILE})" - touch "${OPT_FILE}" - sed -i "s/^${OPT_PREFIX}${opt_name}*$//" "${OPT_FILE}" - echo "local ${OPT_PREFIX}${opt_name}=\"${opt_value}\"" >> "${OPT_FILE}" -} - -function exec_hooks { - - local hook=${1} - local workspace=${2} - - local hook_scripts=$( find "${HOOKS_DIR}" -type f -name "*${hook}" -executable | sort ) - - for hook_script in ${hook_scripts}; do - - info "[>> ${hook}] ${hook_script}" - - ( cd "${workspace}" && "${hook_script}" ) 2> >(stderr) 1> >(stdout) - - # If the script did not execute properly, we stop here - if [ $? != 0 ]; then - fatal "The '${hook_script}' hook script did not finished properly !" - fi - - info "[<< ${hook}] ${hook_script}" - - done - -} diff --git a/package b/package index a898714..4d95fe7 100755 --- a/package +++ b/package @@ -1,182 +1,117 @@ -#!/usr/bin/env bash +#!/usr/bin/env python3 -set -e +import argparse, sys, shutil, os, subprocess -TAMARIN_VERSION=0.0.1 -TAMARIN_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +sys.path.append(os.path.dirname(__file__) + '/lib') -BASE_DIR="$TAMARIN_DIR" source "$TAMARIN_DIR/lib/util.sh" -DEFAULT_64_IMAGE="debian:jessie" -DEFAULT_32_IMAGE="32bit/debian:jessie" +import tamarin -function show_usage { - echo - echo "Usage: $0 -p project_path [-a arch] [-d destination] [-i image] [-k]" - echo - echo "Parameters: " - echo - echo " -p Path to the project to build" - echo " -v Optional : Show Tamarin version" - echo " -a Optional : Target architecture (default amd64)" - echo " -d Optional : Destination of the builed packages (default ./packages)" - echo " -i Optional : Name of the Docker image to use for build (default: debian:jessie)" - echo " -k Optional : Keep the Docker container after build " - echo " -B Optional : Build branch (for git projects only) (default dist/ubuntu/precise/master)" - echo - exit 2 -} +def create_args_parser(): + '''Return a new configured ArgumentParser''' + profile_names = tamarin.get_available_profile_names() -# Create a build container based on the $BASE_IMAGE argument -function create_container { + parser = argparse.ArgumentParser(description="Generate packages for various GNU/Linux distributions") - # Escape image name - local escaped_basename=$(echo "$BASE_IMAGE" | sed 's/[^a-z0-9\-\_\.]/\_/gi') - # Generate container tag - container_tag="tamarin:${escaped_basename}_$(date +%s)" - # Create temporary dir for the Dockerfile - local temp_dir="$(mktemp -d)" + # Define available/required arguments and flags + parser.add_argument("project_directory", help="The path to your project to package") + parser.add_argument("-o", "--output", help="The path to the generated packages destination directory", default=".") + parser.add_argument("-p", "--profile", help="The profile to use to package this project (default: debian)", choices=profile_names, default='debian') + parser.add_argument("-a", "--architecture", help="The target architecture for the package (default: amd64)", default='amd64') + parser.add_argument("-b", "--base", help="Use the specified image instead of the profile's one", default='') + parser.add_argument("--rebuild", help="Ignore cache and rebuild container's image", action="store_true", default=False) + parser.add_argument("--debug", help="Will add extra output and start the container in interactive mode", action="store_true", default=False) + parser.add_argument("--cleanup", help="Clear the workspace and remove obsolete Docker images before build", action="store_true", default=False) - local projectName=${1} + return parser - # Link lib & hooks folders - ln -s $(readlink -f "$TAMARIN_DIR/lib") "$temp_dir/lib" - ln -s $(readlink -f "$TAMARIN_DIR/hooks") "$temp_dir/hooks" +def build_image(build_workspace, base_image, profile_name, profile, debug=False, rebuild=False): - # Create Dockerfile - cat << EOF > "$temp_dir/Dockerfile" - FROM ${BASE_IMAGE} + with open("{:s}/Dockerfile".format(build_workspace), 'w') as dockerfile: + dockerfile.write("FROM {:s}\n".format(base_image)) - ENV DEBIAN_FRONTEND noninteractive + # Configure "containerbuild" hooks environment + hooks_env = os.environ.copy() + hooks_env["PATH"] = os.environ['PATH'] + ':' + tamarin.get_lib_dir() - RUN apt-get update &&\ - apt-get install --yes --no-install-recommends build-essential devscripts equivs + # Run hooks + tamarin.run_profile_hooks(profile, 'containerbuild', cwd=build_workspace, env=hooks_env, debug=debug) - RUN mkdir /root/.tamarin - RUN mkdir /project + image_tag = "tamarin:{:s}_{:s}_{:d}".format(profile_name, base_image.replace(':', '_'), os.getpid()) - ADD ./lib /root/.tamarin/lib - ADD ./hooks /hooks - RUN chmod +x /root/.tamarin/lib/build.sh + build_args = [ "build", "-t", image_tag ] - VOLUME /src - VOLUME /dist + if rebuild: + build_args += [ "--no-cache" ] -EOF + tamarin.run_docker(build_args + [build_workspace], debug=debug) - exec_hooks "containerbuild" "$temp_dir" + return image_tag - echo "CMD /root/.tamarin/lib/build.sh ${projectName} ${BUILD_BRANCH} /tmp ${BUILD_TAG}" >> "$temp_dir/Dockerfile" +def cleanup(build_workspace=None, debug=False): - # Build image - tar -C "$temp_dir" -czh . | docker build -t "$container_tag" - 2> >(stderr) 1> >(stdout) + if build_workspace == None: + build_workspace = tamarin.get_workspace_subdir('tmp') - # Delete temporary folder - rm -rf "$temp_dir" + # Suppression de l'espace de travail de build + shutil.rmtree(build_workspace, ignore_errors=True) -} +def validate_args(args): + '''TODO''' -# Main function -function main { +if __name__ == "__main__": - info "Building container from $BASE_IMAGE..." + parser = create_args_parser() + args = parser.parse_args() - local project_name=$(basename ${PROJECT_PATH}) + validate_args(args) - # Create container & "$container_tag" variable - create_container ${project_name} + if args.cleanup: + cleanup(debug=args.debug) - local docker_opt="run -e \"DISTRIB=$BASE_IMAGE\" -e \"PROJECT_NAME=$project_name\"" + # Verify project directory + project_dir = os.path.abspath(args.project_directory) + output_dir = os.path.abspath(args.output) - # Expose host uid & gid - docker_opt="${docker_opt} -e HOST_UID=$(id -u) -e HOST_GID=$(id -g)" + # Load build profile + profile = tamarin.load_profile(args.profile, debug=args.debug) - # Expose host proxy variables - docker_opt="${docker_opt} -e HTTP_PROXY=${HTTP_PROXY} -e HTTPS_PROXY=${HTTPS_PROXY}" - docker_opt="${docker_opt} -e http_proxy=${http_proxy} -e https_proxy=${https_proxy}" + workspace = tamarin.get_workspace_dir() - # Target architecture - docker_opt="${docker_opt} -e TARGET_ARCH=${TARGET_ARCH}" + pid = os.getpid() + build_workspace = tamarin.get_workspace_subdir('tmp/build_{:d}'.format(pid)) - # If running in terminal, set docker to interactive - if [[ -t 1 ]]; then - docker_opt="${docker_opt} -it" - fi + base_image = args.base if args.base != '' else profile['profile']['default_image'] - if [[ ${PERSIST_CONTAINER} -eq 0 ]] - then - docker_opt="${docker_opt} --rm " - else - docker_opt="${docker_opt}" - fi + image_tag = build_image(build_workspace, base_image, args.profile, profile, debug=args.debug, rebuild=args.rebuild) - docker_opt="${docker_opt} -v $PROJECT_PATH:/src:ro -v $PROJECT_DEST:/dist:rw $container_tag" + # rkt run arguments + docker_args = [ + "run", + "--rm", + "-v", "{:s}:/src:ro".format(project_dir), + "-v", "{:s}:/dist".format(output_dir), + "-v", "{:s}:/tamarin/hooks:ro".format(tamarin.get_hooks_dir()), + "-v", "{:s}:/tamarin/lib:ro".format(tamarin.get_lib_dir()), + "-v", "{:s}:/tamarin/profiles:ro".format(tamarin.get_profiles_dir()) + ] - info "Switching to container..." - debug "docker ${docker_opt}" - docker ${docker_opt} - res=${?} + # Use environment proxy if defined + for proxy_var in ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']: + if proxy_var in os.environ: + docker_args += ["-e", "{:s}={:s}".format(proxy_var, os.environ[proxy_var])] - success "Done" - return ${res} -} + kwargs = dict() + kwargs['debug'] = args.debug -# -# Parsing options -# -while getopts "vkp:d:i:B:t:a:o:" option -do - case $option in - k) - PERSIST_CONTAINER=1 - ;; - p) - PROJECT_PATH=$(readlink -f ${OPTARG}) - ;; - d) - PROJECT_DEST=$(readlink -f ${OPTARG}) - ;; - i) - BASE_IMAGE="${OPTARG}" - ;; - B) - BUILD_BRANCH=${OPTARG} - ;; - t) - BUILD_TAG=${OPTARG} - ;; - a) - TARGET_ARCH=${OPTARG} - ;; - v) - echo "Tamarin v${TAMARIN_VERSION}" - exit - ;; - *) - show_usage - ;; - esac -done + if args.debug: + kwargs['pty'] = True + docker_args += ["-it", image_tag, "/bin/sh"] + helper_cmd = " ".join(["/usr/bin/python3", "/tamarin/lib/build.py", args.profile, args.architecture]) + print("Executer '{:s}' pour lancer la construction du paquet.".format(helper_cmd)) + else: + docker_args += [image_tag, "/usr/bin/python3", "/tamarin/lib/build.py", args.profile, args.architecture] -[[ -z ${PROJECT_PATH} ]] && show_usage -[[ -z ${PROJECT_DEST} ]] && PROJECT_DEST=$(readlink -f "./packages") -[[ -z ${BUILD_BRANCH} ]] && BUILD_BRANCH="dist/ubuntu/precise/master" -[[ -z ${PERSIST_CONTAINER} ]] && PERSIST_CONTAINER=0 -[[ -z ${TARGET_ARCH} ]] && TARGET_ARCH=amd64 + # Start container + tamarin.run_docker(docker_args, **kwargs) -if [[ "${TARGET_ARCH}" =~ ^i[0-9]86$ ]] && [ -z "${BASE_IMAGE}" ]; then - info "32bit architecture specified and no specific image given, switching to default 32bit image..." - BASE_IMAGE=${DEFAULT_32_IMAGE} -fi - -[[ -z ${BASE_IMAGE} ]] && BASE_IMAGE=${DEFAULT_64_IMAGE} - -# -# Warn user about "proxy" -# - -if [[ -n ${http_proxy} ]] -then - warn "You have a proxy defined please make sure docker deamon is configured to use this proxy" -fi - -main + cleanup(build_workspace, debug=args.debug) diff --git a/profiles/debian.conf b/profiles/debian.conf new file mode 100644 index 0000000..8fb00a0 --- /dev/null +++ b/profiles/debian.conf @@ -0,0 +1,27 @@ +# Configuration générale du profil +[profile] +# Image Docker par défaut +default_image=debian:jessie + +# Configuration de l'étape de pré-construction du conteneur +[containerbuild] +hooks=containerbuild/debian/install-build-essential, + containerbuild/debian/install-git + +# Configuration de l'étape de pré-construction du paquet +[prebuild] +hooks=prebuild/debian/load-project-db, + prebuild/debian/copy-sources-to-workspace, + prebuild/debian/complete-project-db, + prebuild/debian/create-changelog, + prebuild/debian/create-dummy-changelog, + prebuild/debian/add-package-version-suffix, + prebuild/debian/install-build-depends + +# Configuration de l'étape de construction du paquet +[build] +hooks=build/debian/build + +# Configuration de l'étape de post-construction du paquet +[postbuild] +hooks=postbuild/debian/export-dist-postbuild diff --git a/test/data/debian/dummy-project/debian/changelog b/test/data/debian/dummy-project/debian/changelog new file mode 100644 index 0000000..5c78aa0 --- /dev/null +++ b/test/data/debian/dummy-project/debian/changelog @@ -0,0 +1,5 @@ +dummy-project (0.0.1) unstable; urgency=low + + * Dev release + + -- William Petit Fri, 16 Oct 2015 15:57:03 +0200 diff --git a/test/data/debian/dummy-project/debian/compat b/test/data/debian/dummy-project/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/test/data/debian/dummy-project/debian/compat @@ -0,0 +1 @@ +9 diff --git a/test/data/debian/dummy-project/debian/control b/test/data/debian/dummy-project/debian/control new file mode 100644 index 0000000..a628610 --- /dev/null +++ b/test/data/debian/dummy-project/debian/control @@ -0,0 +1,13 @@ +Source: dummy-project +Section: unknown +Priority: optional +Maintainer: William Petit +Build-Depends: debhelper (>= 8.0.0) +Standards-Version: 3.9.4 +Homepage: +Vcs-Git: https://forge.cadoles.com/wpetit/tamarin.git +Vcs-Browser: https://forge.cadoles.com/wpetit/tamarin + +Package: dummy-project +Architecture: any +Description: Projet test pour la construction de paquets via Tamarin diff --git a/test/data/debian/dummy-project/debian/rules b/test/data/debian/dummy-project/debian/rules new file mode 100644 index 0000000..ecf4506 --- /dev/null +++ b/test/data/debian/dummy-project/debian/rules @@ -0,0 +1,8 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + +%: + dh $@ diff --git a/test/data/debian/dummy-project/debian/source/format b/test/data/debian/dummy-project/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/test/data/debian/dummy-project/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/test/package_test.py b/test/package_test.py new file mode 100644 index 0000000..d600484 --- /dev/null +++ b/test/package_test.py @@ -0,0 +1,16 @@ +import unittest, os +from importlib.machinery import SourceFileLoader + +package = SourceFileLoader('package', os.path.dirname(__file__) + '/../package').load_module() + +class TestPackage(unittest.TestCase): + + def test_args_parser(self): + parser = package.create_args_parser() + + def test_download_and_extract_rkt(self): + """TODO""" + + +if __name__ == '__main__': + unittest.main()