jenkins-pipelines/scripts/clone-source.sh

259 lines
8.5 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (c) 2022 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
set -e
source $(dirname "$0")/lib/job_utils.sh
source $(dirname "$0")/lib/retries.sh
require_job_env BUILD_HOME
require_job_env DRY_RUN
require_job_env REFRESH_SOURCE
declare_job_env PATCH_LIST
# Some jenkins versions have a bug that causes misbehavior
# when a multi-line parameter is empty. Treat "-" as empty
# as a workaround.
PATCH_LIST="$(echo "$PATCH_LIST" | grep -v -E '^\s*(#.*)?$' | sed -r -e 's/^\s+//g' -e 's/\s+$//g')"
if [[ "$PATCH_LIST" == "-" ]] ; then
PATCH_LIST=
fi
load_build_env
RETRIES=3
RETRY_INTERVAL_SEC=15
notice "initializing source repo" \
"BUILD_HOME=$BUILD_HOME"
mkdir -p "$BUILD_HOME"
mkdir -p "$BUILD_HOME/$REPO_ROOT_SUBDIR"
mkdir -p "$BUILD_HOME/$WORKSPACE_ROOT_SUBDIR"
ln -sfn "$REPO_ROOT_SUBDIR" "$REPO_ROOT"
ln -sfn "$WORKSPACE_ROOT_SUBDIR" "$WORKSPACE_ROOT"
shell() {
if [[ "$1" == "--dry-run" ]] ; then
echo ">>> (dry) running:" >&2
echo "$2" | sed -r 's#^#\t#' >&2
return
fi
echo ">>> running" >&2
echo "$1" | sed -r 's#^#\t#' >&2
( eval "$1" ; )
}
cd "$REPO_ROOT"
# If repo doesn't exist or REFRESH_SOURCE==true: discard all local changes
# and re-pull all repos
if [[ ! -d ".repo" || ! -f ".repo-init-done" ]] || $REFRESH_SOURCE ; then
notice "Cloning sources"
if [[ ! -d ".repo" || ! -f ".repo-init-done" ]] ; then
dry_run=false
dry_run_arg=
else
dry_run=$DRY_RUN
dry_run_arg=$DRY_RUN_ARG
fi
# Don't change this file name: the builder container relies on it!
$dry_run || rm -f ".repo-init-done"
# pull repo source checkout explicitly
if [[ -d .repo/repo/.git ]] ; then
# ignore errors here in case something is wrong with this git directory
# a subsequent "repo init" or "repo clone" will attempt to re-create it
with_default_retries shell $dry_run_arg "cd .repo/repo && git pull --no-rebase --ff-only" || true
fi
# clone manifest
with_default_retries shell $dry_run_arg "repo init -u \"$MANIFEST_URL\" -b \"$MANIFEST_BRANCH\" -m \"$MANIFEST\""
# clean up gits: abort in-progress "rebase" & "am" commands &
# discard modified files
for d in $(repo forall -c 'echo $REPO_PATH' 2>/dev/null) ; do
[[ -d "$d" ]] || continue
shell $dry_run_arg "\
set -e ;
cd \"$d\"
git rebase --abort >/dev/null 2>&1 || :
git am --abort >/dev/null 2>&1 || :
git cherry-pick --abort >/dev/null 2>&1 || :
git clean -d -f
git reset --hard HEAD
"
done
# pull gits
with_default_retries shell $dry_run_arg "repo sync --force-sync --force-remove-dirty -j4"
touch ".repo-init-done"
else
info "repo already cloned"
fi
# Apply patches in "$PATCH_LIST" parameter given one per line:
# [PATH] URL REF
if [[ -n "$PATCH_LIST" ]] ; then
notice "Applying patches"
ALL_PROJECTS="$(repo forall -c 'echo $REPO_PROJECT $REPO_PATH')"
# Helper func to determine whether a patch URL refers to the manifest repo
is_manifest_url() {
local path="$1"
local url="$2"
[[ -z "$path" ]] || path="$(realpath -m -s "$path" --relative-to=.)" || return 1
# path given
if [[ "$path" == ".repo/manifests" ]] ; then
return 0
fi
# url equals MANIFEST_URL exactly
if [[ "$url" == "$MANIFEST_URL" ]] ; then
return 0
fi
# url matches any of the manifest repo remote URLs on disk
all_urls=$( (cd .repo/manifests && git config --list | sed -r -n 's#^remote[.][^.]+[.]url=##gp' | sort -u) 2>/dev/null || :)
if [[ -n "$all_urls" ]] && in_list "$url" ${all_urls} ; then
return 0
fi
# give up
return 1
}
# Helper func to find a sub-project by path & url
# If user provided a path, look for the matching project
# Else, find project that matches URL's basename
# Prints the line "PROJECT PATH"
find_project() {
local path="$1"
local url="$2"
local spec
# path not provided: look for project that matches the URL's basename
# with or without .git at the end
if [[ -z "$path" ]] ; then
local url_base="${url##*/}"
local url_base_norm="${url_base%.git}"
local url_base_git="${url_base_norm}".git
local fail=0
while read spec_project spec_project_path ; do
[[ -n "$spec_project" ]] || continue
local spec_project_norm="${spec_project%.git}"
local spec_project_git="${spec_project_norm}.git"
if [[ "$spec_project_norm" == "$url_base_norm" || "$spec_project_git" == "$url_base_git" ]] ; then
if [[ -n "$path" ]] ; then
error "patch url \"$url\" matches more than one project:" \
" ${spec_project_path}" \
" ${path}" \
"Please specify project path explicitly in front of the URL, eg:" \
" ${path} ${url} ${ref}"
fail=1
continue
fi
echo "$spec_project $spec_project_path"
return 0
fi
done <<<"$ALL_PROJECTS"$'\n'
# Errors: return false
[[ $fail -eq 0 ]] || return 1
# path provided: look for project matching this path
else
path="$(realpath -m -s "$path" --relative-to=.)" || return 1
while read spec_project spec_project_path ; do
[[ -n "$spec_project" ]] || continue
spec_project_path="$(realpath -m -s "$spec_project_path" --relative-to=.)"
if [[ "$path" == "$spec_project_path" ]] ; then
echo "$spec_project $spec_project_path"
return 0
fi
done <<<"$ALL_PROJECTS"$'\n'
fi
# if we reach here, we couldn't find the project
error "unable to find project matching $path $url $ref"
return 1
}
# All patches, each element is a string:
# PROJECT PATH URL REF
PATCH_SPECS="$(
fail=0
while read line ; do
# skip empty lines
if [[ -z "$line" || "${line[0]}" == "#" ]] ; then
continue
fi
# split by whitespace
declare -a parts=($line)
if [[ "${#parts[@]}" -eq 2 ]] ; then
path=
url="${parts[0]}"
ref="${parts[1]}"
elif [[ "${#parts[@]}" -eq 3 ]] ; then
path="${parts[0]}"
url="${parts[1]}"
ref="${parts[2]}"
else
error "Invalid patch spec \"$line\"" \
"Expecting [PATH] URL REF"
fail=1
continue
fi
# manifest ?
if is_manifest_url "$path" "$url" ; then
project="-"
path=".repo/manifests"
else
# guess project and/or path
tmp="$(find_project "$path" "$url")" || return 1
read project path <<<"$tmp" || return 1
fi
echo "$project $path $url $ref"
done <<<"$PATCH_LIST"$'\n'
[[ $fail -eq 0 ]] || exit 1
)" || exit 1
# Reset each project that requires patching. We need to do this
# first in case PATCH_LIST includes multiple patches for the same repo,
# and in case we didn't run "repo sync" due to REFRESH_SOURCE=false
while read project path ; do
[[ -n "$project" ]] || continue
# abort in-progress "rebase" etc
info "" "--- $path: resetting git checkout" ""
shell $DRY_RUN_ARH "\
set -e
cd \"$path\"
git rebase --abort >/dev/null 2>&1 || :
git am --abort >/dev/null 2>&1 || :
git cherry-pick --abort >/dev/null 2>&1 || :
git checkout .
"
# manifest: checkout "default" branch
if [[ $path == ".repo/manifests" ]] ; then
shell $DRY_RUN_ARG "cd \"$path\" && git checkout default"
# Other projects: checkout whatever manifest points to
else
shell $DRY_RUN_ARG "cd \"$path\" && repo sync --no-manifest-update --local-only --detach $project"
fi
done <<<"$(echo "$PATCH_SPECS" | awk '{print $1, $2}' | sort -u)"
# Apply patches one at a time
while read project path url ref ; do
info "" "--- $path: applying patch $url $ref" ""
shell $DRY_RUN_ARG "cd \"$path\" && git fetch \"$url\" \"$ref\" && git cherry-pick FETCH_HEAD"
done <<<"$(echo "$PATCH_SPECS")"
fi