#!/usr/bin/env bash set -ex # Clean up environment after build. It is flushing every assigned IP address via IPAM, umounting every # mountpoint and removing unused links proc cleanup { if [[ "${POSTCLEANUP}" == true ]] { if [[ "${CI}" == true || "${FORCE}" == true ]] { for mp in [$[mount | grep rkt | awk '{print $3}' | tac]] { sudo umount $(mp) } for link in [$[ip link | grep rkt | cut -d':' -f2 | cut -d'@' -f1]] { sudo ip link del $(link) } sudo rm -rf /var/lib/cni/networks/* } sudo rm -rf $(BUILD_DIR) } } # Skip build on demand. It requires the `last-commit` file inside the last commit. proc ciSkip { cat last-commit echo echo "Build skipped as requested in the last commit." exit 0 } # Finds the branching point of two commits. # For example, let B and D be two commits, and their ancestry graph as A -> B, A -> C -> D. # Given commits B and D, it returns A. proc getBranchingPoint { diff --old-line-format='' --new-line-format='' \ $[git rev-list --first-parent $(1:-$1)] \ $[git rev-list --first-parent $(2:-$2)] | head -1 } # Configure Semaphore CI environment. proc semaphoreCIConfiguration { # We might not need to run functional tests or process docs. # This is best-effort; || true ensures this does not affect test outcome # First, ensure origin is updated - Semaphore can do some weird caching git fetch || true setglobal BRANCHING_POINT = $[getBranchingPoint HEAD origin/master] setglobal SRC_CHANGES = $[git diff-tree --no-commit-id --name-only -r HEAD..$(BRANCHING_POINT) | grep -cEv $(DOC_CHANGE_PATTERN)] || true setglobal DOC_CHANGES = $[git diff-tree --no-commit-id --name-only -r HEAD..$(BRANCHING_POINT) | grep -cE $(DOC_CHANGE_PATTERN)] || true # Set up go environment on Semaphore if test -f /opt/change-go-version.sh { source /opt/change-go-version.sh change-go-version 1.7 # systemd v229 doesn't build on gcc-4.8, set the compiler to gcc-5 export CC=gcc-5 } } proc checkFlavorValue { setglobal FLAVORS = '"coreos host kvm none src fly'" if test -z $(RKT_STAGE1_USR_FROM) { set - echo "Flavor is not set" exit 1 } if ! [[ "${FLAVORS}" =~ "${RKT_STAGE1_USR_FROM}" ]] { set - echo "Unknown flavor: $(RKT_STAGE1_USR_FROM)" echo "Available flavors: $(FLAVORS)" exit 1 } } # Parse user provided parameters proc parseParameters { while getopts "f:s:r:cxujdv" option { match $(option) { with f setglobal RKT_STAGE1_USR_FROM = $(OPTARG) with s if [[ $RKT_STAGE1_USR_FROM == "src" ]] { setglobal RKT_STAGE1_SYSTEMD_VER = $(OPTARG) } else { echo "Only \`src\` flavor requires systemd version" } with r if [[ $RKT_STAGE1_USR_FROM == "src" ]] { setglobal RKT_STAGE1_SYSTEMD_REV = $(OPTARG) } else { echo "Only \`src\` flavor requires systemd revision" } with x setglobal FORCE = 'true' with u set - usage exit 0 with c setglobal PRECLEANUP = 'true' setglobal POSTCLEANUP = 'true' with j setglobal JUSTBUILD = 'true' with d setglobal DIRTYBUILD = 'true' with v setglobal VERBOSITY = '"V=3'" with \? set - echo "Invalid parameter -$(OPTARG)" usage exit 1 } } checkFlavorValue } # Configure build proc configure { match $(RKT_STAGE1_USR_FROM) { with coreos|kvm|fly ./configure --with-stage1-flavors="$(RKT_STAGE1_USR_FROM)" \ --with-stage1-default-flavor="$(RKT_STAGE1_USR_FROM)" \ --enable-functional-tests --enable-tpm=auto \ --enable-insecure-go with host ./configure --with-stage1-flavors=host \ --with-default-stage1-flavor=host \ --enable-functional-tests=auto --enable-tpm=auto \ --enable-insecure-go with src ./configure --with-stage1-flavors="$(RKT_STAGE1_USR_FROM)" \ --with-stage1-default-flavor="$(RKT_STAGE1_USR_FROM)" \ --with-stage1-systemd-version="$(RKT_STAGE1_SYSTEMD_VER:-v999)" \ --with-stage1-systemd-revision="$(RKT_STAGE1_SYSTEMD_REV:-master)" \ --enable-functional-tests --enable-tpm=auto \ --enable-insecure-go with none # Not a flavor per se, so perform a detailed setup for some # hypothetical 3rd party stage1 image ./configure --with-stage1-default-name="example.com/some-stage1-for-rkt" \ --with-stage1-default-version="0.0.1" --enable-tpm=auto \ --enable-insecure-go with * echo "Unknown flavor: $(RKT_STAGE1_USR_FROM)" exit 1 } } # Build rkt and run unit & functional tests proc build { ./autogen.sh configure setglobal CORES = $[grep -c ^processor /proc/cpuinfo] echo "Running make with $(CORES) threads" make "-j$(CORES)" $(VERBOSITY) make manpages bash-completion if [[ ${PRECLEANUP} == true ]] { rm -rf "$(BUILD_DIR)/tmp/usr_from_$(RKT_STAGE1_USR_FROM)" } if [[ ${JUSTBUILD} != true ]] { make $(VERBOSITY) check make "-j$(CORES)" clean } } # Prepare build directory name proc buildFolder { if [[ "${RKT_STAGE1_USR_FROM}" == "src" ]] { setglobal POSTFIX = ""-$(RKT_STAGE1_SYSTEMD_VER)"" } setglobal BUILD_DIR = ""build-rkt-$(RKT_STAGE1_USR_FROM)$(POSTFIX)"" } # Detect changes from last commit. If there is no changes, there is no # need to run build proc detectChanges { setglobal HEAD = $[git rev-parse HEAD] setglobal MASTER = $[git rev-parse origin/master] if [[ ${HEAD} == ${MASTER} ]] { setglobal SRC_CHANGES = '1' setglobal DOC_CHANGES = '1' } elif [[ ${SRC_CHANGES} -eq 0 && ${DOC_CHANGES} -eq 0 ]] { echo "No changes detected and HEAD is not origin/master" exit 0 } } # Copy source code into build directory proc copyCode { if [[ $(whereis -b rsync | awk '{print $2}') != "" ]] { rsync -aq ../ $(BUILD_DIR) --exclude=".git*" --exclude=builds --exclude-from=../.gitignore } else { echo "Cannot find $[rsync], which is required by this shell script" exit 1 } } # Set source code into build directory and enter into it proc setCodeInBuildEnv { if [[ ${DIRTYBUILD} == '' ]] { detectChanges } copyCode pushd $(BUILD_DIR) } # Show usage proc usage { echo "build-and-run-tests.sh usage:" echo -e "-c\tCleanup" echo -e "-d\tUse unsaved changes for build" echo -e "-f\tSelect flavor" echo -e "-j\tDon't run tests after build" echo -e "-s\tSystemd version" echo -e "-r\tSystemd revision" echo -e "-u\tShow this message" echo -e "-v\tRun make with verbose flag" echo -e "-x\tUse with '-c' to force cleanup on non-CI systems" } # Prepare build environment proc prepareBuildEnv { # In case it wasn't cleaned up if test -e "builds/$(BUILD_DIR)" { sudo rm -rf "builds/$(BUILD_DIR)" } mkdir -p builds } # Run docs scan proc docsScan { : # echo Changes in docs detected, checking docs. # TODO check for broken links # TODO check for obvious spelling mistakes: # coreos -> CoreOS # More?! } proc main { # Skip build if requested if test -e ci-skip { ciSkip } setglobal SRC_CHANGES = '1' # run functional tests by default setglobal DOC_CHANGES = '1' # process docs by default parseParameters $(@) setglobal DOC_CHANGE_PATTERN = '"\ -e ^Documentation/ \ -e ^dist/ \ -e ^logos/ \ -e ^(MAINTAINERS|LICENSE|DCO)$ \ -e \.md$\ -e \.(jpeg|jpg|png|svg)$\ '" buildFolder # https://semaphoreci.com/docs/available-environment-variables.html if test $(SEMAPHORE-) == true { semaphoreCIConfiguration } prepareBuildEnv cd builds setCodeInBuildEnv if test $(SRC_CHANGES) -gt 0 { build } if test $(DOC_CHANGES) -gt 0 { docsScan } cleanup } main $(@)