# bash # # Copyright (c) 2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # source $(dirname "${BASH_SOURCE[0]}")/utils.sh || exit 1 source $(dirname "${BASH_SOURCE[0]}")/glob_utils.sh || exit 1 # used by need_build() below NEED_BUILD_PATTERNS=( "! cgcs-root/stx/docs/*" "! cgcs-root/stx/test/*" "! cgcs-root/local-build-data/*" "! cgcs-root/wrs/docs/*" " cgcs-root/wrs/titanium-tools/docker-images/*" # used by *_SDK_Build " cgcs-root/wrs/titanium-tools/lab/*" "! cgcs-root/wrs/titanium-tools/*" # " stx-tools/centos-mirror-tools/yum.conf.sample" # " stx-tools/centos-mirror-tools/config/centos/$LAYER" # "! stx-tools/*" " cgcs-root/build-tools/signing/*" " cgcs-root/build-tools/certificats/*" " cgcs-root/build-tools/build-docker-images/*" " cgcs-root/build-tools/build-wheels/*" "! cgcs-root/build-tools/*" ) # Usage: ( cd $MY_REPO_ROOT_DIR && print_last_commits ; ) >LAST_COMMITS print_last_commits() { local dir padded_dir for dir in ./.repo/manifests $( find . \( -path './.repo' -prune \) -o \( -xtype d -name .git -printf '%h\n' \) | sort ) ; do pushd "$dir" >/dev/null || exit 1 padded_dir="$(printf "%-52s" "$dir")" git log --pretty=tformat:"$padded_dir %H" -n 1 || exit 1 popd >/dev/null || exit 1 done } # # Usage: diff_last_commits MY_WORKSPACE/../LAST_COMMITS [PATTERNS...] # # Diff git HEADs with a LAST_COMMITS file generated by another build # Return true (0) if there are no differences # # PATTERNS may be used to filter out changes in some files when comparing # # If any file changed since LAST_COMMITs matches a PATTERN, we return # false (ie changes detected & a rebuild is required). If it matches a # "negative" pattern that begins with "!", we continue to the next file # instead. Patterns are matched in order until a match is found. A # combination of positive and negative patterns may be used to skip parts # of the source tree. # # Patterns are similar to shell glob patterns, except "*" and "?" match # any character including "/". # Leading and trailing whitespace as well as leading "./" in patterns # are not significant. # Changes in files that didn't match any patterns are treated as positive # matches. # # EXAMPLE: # # diff_last_commits $MY_WORKSPACE/../LAST_COMMITS \ # # detect changes in this file \ # "stx-tools/centos-mirror-tools/yum.conf.sample" \ # # ignore other changes under centos-mirror-tools \ # "!stx-tools/centos/mirror-tools/*" \ # # detect changes everywhere else (implied) # "*" # diff_last_commits() { local last_commits_file="$1" ; shift || : local debug=2 local dir dir_regex local last_commit local commit_files_str local -a commit_files local file local match pattern_expr pattern regex # no previous builds: assume builds different if [[ ! -f "$last_commits_file" ]] ; then [[ "$debug" == 0 ]] || echo "## file \`$last_commits_file' doesn't exist: return false" >&2 return 1 fi # find all gits [[ "$debug" == 0 ]] || echo "## looking for diffs between \`$PWD' and \`$last_commits_file'" >&2 for dir in ./.repo/manifests $( find . \( -path './.repo' -prune \) -o \( -xtype d -name .git -printf '%h\n' \) | sort ) ; do [[ "$debug" == 0 ]] || echo "## checking \`$dir'" >&2 if [[ "$dir" == "." ]] ; then dir_prefix="" else dir_prefix="${dir#./}"/ fi # find last commit for this dir # create a regex LAST_COMMITS, eg: ./cgcs-root/stx/config => ^[.][/]cgcs-root[/]stx[/]config[^a-zA-Z0-9/_-] dir_regex="$(echo "$dir" | sed \ -e 's:/:[/]:g' \ -e 's:$:[^a-zA-Z0-9/_-]:' \ -e 's:^[.][.]:^[.][.]:' \ -e 's:^[.]:^[.]:' \ )" last_commit=$(grep "$dir_regex" "$last_commits_file" | awk ' { print $2 } ') # it didn't exist in previous buid: assume builds different if [[ -z "$last_commit" ]] ; then [[ "$debug" == 0 ]] || echo "## $dir: not present in \`$last_commits_file': return false" >&2 return 1 fi # get all files changed since last_commit commit_files_str="$(cd "$dir" && git diff-tree --no-commit-id --name-only -r $last_commit..HEAD)" || exit 1 readarray -t commit_files < <(echo -n "$commit_files_str") # check each file against PATTERNs for file in "${commit_files[@]}" ; do match=0 for pattern_expr in "$@" ; do # convert glob pattern to regex pattern="$(echo "$pattern_expr" | sed -r -e 's/^\s*[!]?\s*//' -e 's/\s*$//')" regex="$(glob_to_basic_regex "$pattern")" [[ "$debug" -lt 2 ]] || echo "## trying to match pattern \`$pattern' / regex \`$regex'" >&2 # check if the file matches if echo "${dir_prefix}$file" | grep -q -E "$regex" >/dev/null || \ echo "$dir/$file" | grep -q -E "$regex" >/dev/null ; then # pattern doesn't begin with "!": assume builds different if ! echo "$pattern_expr" | grep -q -E '^\s*[!]' ; then [[ "$debug" == 0 ]] || echo "## file \`${dir_prefix}$file' matched positive pattern \`$pattern_expr': return false" >&2 return 1 fi # "!" pattern: continue to next file [[ "$debug" == 0 ]] || echo "## file \`${dir_prefix}$file' matched negative pattern \`$pattern_expr': continue to next file" >&2 match=1 break fi done # for pattern_expr ... if [[ $match == 0 ]] ; then [[ "$debug" == 0 ]] || echo "## file \`${dir_prefix}$file' didn't match any negative patterns: return false" >&2 return 1 fi done # for file ... done # for dir ... [[ "$debug" == 0 ]] || echo "## no diffs found: return true" >&2 return 0 } # # Usage: print_changelog LAST_COMMITS_FILE [DEFAULT_FROM_TIMESTAMP] # # Print out the change log since LAST_COMMITS_FILE. # # DEFAULT_FROM_TIMESTAMP will be used for repos missing from LAST_COMMITS_FILE # and must be a date or date/time in ISO format. Defaults to the value of # BUILD_TIMESTAMP global variable minus 1 day at midnight, or yesterday's midnight. # # LAST_COMMITS_FILE need not exist. # print_changelog() { local last_commits_file="$1" local default_from_timestamp if [[ -n "$2" ]] ; then default_from_timestamp="$2" else local build_date build_date="${BUILD_TIMESTAMP:0:10}" [[ -n "$build_date" ]] || build_date=$(date '+%Y-%m-%d') || return 1 default_from_timestamp="$(date --date="$build_date - 1 day" '+%Y-%m-%d 00:00:00')" || return 1 fi local dir for dir in ./.repo/manifests $( find . \( -path './.repo' -prune \) -o \( -xtype d -name .git -printf '%h\n' \) | sort ) ; do ( set -e padded_dir="$(printf "%-52s" "$dir")" commit= if [[ -f "$last_commits_file" ]] ; then # create a regex LAST_COMMITS, eg: ./cgcs-root/stx/config => ^[.][/]cgcs-root[/]stx[/]config[^a-zA-Z0-9/_-] regex="$(echo "$dir" | sed \ -e 's:/:[/]:g' \ -e 's:$:[^a-zA-Z0-9/_-]:' \ -e 's:^[.][.]:^[.][.]:' \ -e 's:^[.]:^[.]:' \ )" commit=$(grep "$regex" "$last_commits_file" | awk ' { print $2 } ') fi if [[ -n "$commit" ]] ; then git_log_args=("$commit..") else git_log_args=(--after "$default_from_timestamp") fi pushd "$dir" >/dev/null git log --date=iso --pretty=tformat:"$padded_dir %H %cd%x09%cn%x09%s" "${git_log_args[@]}" popd >/dev/null ) ; done } # # Usage: print_changelog_since TIMESTAMP # print_changelog_since() { print_changelog "" "$1" } # Usage: create_standard_changelogs # # Create changelog files in $MY_WORKSPACE: # # CHANGELOG # changes since LAST_COMMITS left by most recent successful build, # used for rebuild calculations # # CHANGELOG.OLD # changes since midnight of previous day (24-48 hours) # # CHANGELOG.IMG_DEV # changes since LAST_COMMITS left by most recent dev images build # # CHANGELOG.IMG_STABLE # changes since LAST_COMMITS left by most recent stable images build # # LAST_COMMITS # SHA's of each git's HEAD # create_standard_changelogs() { require_env "MY_REPO_ROOT_DIR" "MY_WORKSPACE" local deploy_dir="${DEPLOY_DIR:-$MY_WORKSPACE/..}" local changelog_file="$MY_WORKSPACE/CHANGELOG" local build_date build_date="${BUILD_TIMESTAMP:0:10}" [[ -n "$build_date" ]] || build_date=$(date '+%Y-%m-%d') || return 1 local default_from_timestamp default_from_timestamp="$(date --date="$build_date - 1 day" '+%Y-%m-%d 00:00:00')" || return 1 # CHANGELOG echo "## creating $changelog_file (since last iso build)" ( set -e cd "$MY_REPO_ROOT_DIR" print_changelog "$deploy_dir/LAST_COMMITS" "$default_from_timestamp" ) >"$changelog_file" || exit 1 # CHANGELOG.OLD echo "## creating $changelog_file.OLD (since yesterday midnight)" ( set -e cd "$MY_REPO_ROOT_DIR" print_changelog "" "$default_from_timestamp" ) >"$changelog_file.OLD" || exit 1 # CHNAGELOG.IMG_DEV echo "## creating $changelog_file.IMG_DEV (since last dev images build)" ( set -e cd "$MY_REPO_ROOT_DIR" print_changelog "$deploy_dir/LAST_COMMITS_IMG_DEV" "$default_from_timestamp" ) >"$changelog_file.IMG_DEV" || exit 1 # CHNAGELOG.IMG_STABLE echo "## creating $changelog_file.IMG_STABLE (since last stable images build)" ( set -e cd "$MY_REPO_ROOT_DIR" print_changelog "$deploy_dir/LAST_COMMITS_IMG_STABLE" "$default_from_timestamp" ) >"$changelog_file.IMG_STABLE" || exit 1 # LAST_COMMITS ( set -e cd "$MY_REPO_ROOT_DIR" print_last_commits ) >"$MY_WORKSPACE/LAST_COMMITS" || exit 1 echo "## LAST_COMMITS" >&2 cat "$MY_WORKSPACE/LAST_COMMITS" >&2 echo "## END LAST_COMMITS" >&2 echo "## CHANGELOG" >&2 cat "$changelog_file" >&2 echo "## END CHANGELOG" >&2 } # Usage: need_build [BUILD_DATE] # # Return true if build is required. BUILD_DATE defaults # to BUILD_TIMESTAMP global var, or today's date. # # This will create either a NEED_BUID or NO_BUILD_REQUIRED file # in MY_WORKSPACE. # # If any of these job parameters are set to true, this function returns true: # FORCE_BUILD # BUILD_DOCKER_IMAGES_DEV # BUILD_DOCKER_IMAGES_STABLE # # These job parameters/env vars must be set to the week days when # images should be built, eg: # # BUILD_DOCKER_IMAGES_DAYS_DEV="" # BUILD_DOCKER_IMAGES_DAYS_STABLE="mon tue" # FORCE_BUILD_DAYS="sat" # need_build() { local build_date build_weekday build_reason build_date="$1" [[ -n "$build_date" ]] || build_date="${BUILD_TIMESTAMP:0:10}" [[ -n "$build_date" ]] || build_date=$(date '+%Y-%m-%d') || return 1 build_weekday=$(get_weekday "$build_date") || exit 1 local deploy_dir="${DEPLOY_DIR:-$MY_WORKSPACE/..}" require_env MY_WORKSPACE MY_REPO_ROOT_DIR FORCE_BUILD BUILD_DOCKER_IMAGES_DEV BUILD_DOCKER_IMAGES_STABLE rm -f "$MY_WORKSPACE/NO_BUILD_REQUIRED" "$MY_WORKSPACE/NEED_BUILD" || exit 1 if $FORCE_BUILD ; then build_reason="forced" elif in_list "$build_weekday" $(normalize_weekdays $FORCE_BUILD_DAYS) ; then build_reason="forced:schedule" elif $BUILD_DOCKER_IMAGES_DEV ; then build_reason="dev_images_forced" elif $BUILD_DOCKER_IMAGES_STABLE ; then build_reason="stable_images_forced" elif ! diff_last_commits "$deploy_dir/LAST_COMMITS" "${NEED_BUILD_PATTERNS[@]}" ; then build_reason="changes_detected" elif in_list "$build_weekday" $(normalize_weekdays $BUILD_DOCKER_IMAGES_DAYS_DEV) && \ ! diff_last_commits "$deploy_dir/LAST_COMMITS_IMG_DEV" "${NEED_BUILD_PATTERNS[@]}" ; then build_reason="dev_images_changes_detected" elif in_list "$build_weekday" $(normalize_weekdays $BUILD_DOCKER_IMAGES_DAYS_STABLE) && \ ! diff_last_commits "$deploy_dir/LAST_COMMITS_IMG_STABLE" "${NEED_BUILD_PATTERNS[@]}" ; then build_reason="stable_images_changes_detected" else touch "$MY_WORKSPACE/NO_BUILD_REQUIRED" || exit 1 echo "## No new content. Build not required." return 1 fi echo "REASON=$build_reason" >"$MY_WORKSPACE/NEED_BUILD" || exit 1 echo "## Build required ($build_reason)" return 0 } # # Return true if the build is being forced: # FORCE_BUILD is "true" -- OR # FORCE_BUILD_DAYS matches the build time stamp # is_build_forced() { [[ -f "$MY_WORKSPACE/NEED_BUILD" ]] || return 1 grep -E -q "^\\s*REASON=['\"]?forced:?" "$MY_WORKSPACE/NEED_BUILD" >/dev/null }