contrib: Add a script to update program firmware
Updates firmware from src/program and src/project repos.
BUG=none
TEST=Run script for puff firmware updates.
Change-Id: Ibd0b6321381961fcd8ae651474e5d65efacb3130
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2249203
Reviewed-by: Sam McNally <sammc@chromium.org>
Commit-Queue: Andrew McRae <amcrae@chromium.org>
Tested-by: Andrew McRae <amcrae@chromium.org>
diff --git a/contrib/update_program_fw b/contrib/update_program_fw
new file mode 100755
index 0000000..dfb9778
--- /dev/null
+++ b/contrib/update_program_fw
@@ -0,0 +1,317 @@
+#!/bin/bash
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# TODO(amcrae): Refactor to use common.sh
+#
+# Script to update base firmware in a program's config, and then
+# regenerate the configs of projects that are part of the program.
+# Also updates the firmware manifest, and uploads all of the changes
+# for review.
+#
+# Usage:
+# ./update_program_fw program release [ reviewer ]
+# E.g:
+# ./update_program_fw puff 13291 amcrae@google.com
+#
+# Environment variables:
+# SRC = base of repo tree (default is ~/trunk/src)
+# HOME = home directory
+#
+# Variables
+#
+TEMPDIR=$(mktemp -d -t fw-XXXXXXXXXX)
+CMDNAME=$(basename "$0")
+SRC="${SRC:-/mnt/host/source/src}"
+PATH="${PATH}:${SRC}/config/bin"
+DIGITS="[1-9][0-9][0-9][0-9][0-9]"
+PROG=""
+BRANCH=""
+REVIEWER=""
+PROGRAM_CL=""
+PROGRAM="program.star"
+EDITOR="ex -s"; export EDITOR
+#
+# Common functions
+#
+cleanup() {
+ rm -rf "${TEMPDIR}"
+}
+
+trap "exit 1" HUP INT PIPE QUIT TERM
+trap 'cleanup' EXIT
+
+usage() {
+ if [[ "$#" -ge 1 ]]; then
+ echo "$*"
+ fi
+ echo "Usage: ${CMDNAME} program-name release [ reviewer ]"
+ echo "e.g:"
+ echo " ${CMDNAME} puff 13271 amcrae@google.com"
+ exit 1
+}
+#
+# Abort the update, and clean up branches and CLs
+#
+abort() {
+ echo "$*"
+ echo "Aborting..."
+ CLS=$(gerrit -i --raw search "owner:me status:open hashtag:${BRANCH}")
+ if [[ -n "${CLS}" ]]; then
+ echo "Abandoning uploaded CLs"
+ gerrit -i abandon "${CLS}"
+ fi
+ "cros_workon-${PROG}" stop "chromeos-base/chromeos-firmware-${PROG}"
+ "cros_workon-${PROG}" stop "chromeos-base/chromeos-config-bsp-${PROG}-private"
+ repo abandon "${BRANCH}"
+ exit 1
+}
+#
+# Extract a CL number from the file containing
+# the output of repo upload
+#
+getcl() {
+ CL=$(grep -o "https://chrome-internal-review.googlesource.com/c/chromeos/$1/+/[0-9][0-9]*" "$2")
+ if [[ -z "${CL}" ]]; then
+ cat "$2"
+ abort CL number not found in repo upload output
+ fi
+ echo "${CL}" | grep -o "[0-9][0-9]*"
+}
+#
+# If not on this branch, start a branch
+#
+branch() {
+ if ! (git branch --show-current | grep -q "${BRANCH}"); then
+ repo start "${BRANCH}"
+ else
+ echo "${BRANCH} already exists, skipping repo start"
+ fi
+}
+#
+# Return true if repo has changes.
+changed() {
+ [[ -n $(git status -s) ]]
+}
+#
+# Add a Cq-Depend line to a commit.
+# Use ex as a line editor to insert it.
+#
+amend() {
+ git commit --amend <<EOF
+/^Change-Id/
+i
+$1
+.
+wq
+EOF
+}
+#
+# Validate arguments
+#
+if [[ "$#" -lt 2 ]] || [[ "$#" -gt 3 ]]; then
+ usage
+fi
+#
+# Program must exist as a directory
+#
+PROG="$1"
+PROGDIR="${SRC}/program/${PROG}"
+if [[ ! -d "${PROGDIR}" ]]; then
+ usage "${PROG} is not a valid program (${PROGDIR} missing)"
+fi
+# Release must be a 5 digit number
+RELEASE="$2"
+if [[ ! "${RELEASE}" =~ ^${DIGITS}$ ]]; then
+ usage "release must be a 5 digit number"
+fi
+#
+# Check for reviewer argument.
+#
+if [[ "$#" -eq 3 ]]; then
+ REVIEWER=$3
+fi
+BRANCH="update_${PROG}_fw_${RELEASE}"
+#
+# Update the firmware version in the program config
+# From now on, all errors should invoke 'abort'
+# so that the branches and CLs are cleaned up.
+#
+cd "${PROGDIR}" || exit 1
+branch
+sed "/^ *major_version = ${DIGITS}$/s/${DIGITS}/${RELEASE}/" "${PROGRAM}" > "${TEMPDIR}/new-${PROGRAM}"
+#
+# Verify that only 1-5 characters have changed.
+#
+DIFF=$(cmp -l "${PROGRAM}" "${TEMPDIR}/new-${PROGRAM}" | wc -l)
+if [[ "${DIFF}" -gt 5 ]]; then
+ diff "${PROGRAM}" "new-${TEMPDIR}/${PROGRAM}"
+ abort "${PROGDIR}/${PROGRAM} update error"
+ exit 1
+fi
+#
+# If program config has changed, create a CL.
+#
+if [[ "${DIFF}" -ne 0 ]]; then
+ cp "${TEMPDIR}/new-${PROGRAM}" "${PROGRAM}"
+ git add .
+ git commit -F - <<EOF
+${PROG}: Update firmware to ${RELEASE}
+
+BUG=none
+TEST=FAFT tests on ${PROG}
+EOF
+ repo upload -y "--ht=${BRANCH}" --cbr . > "${TEMPDIR}/upload.output" 2>&1
+ PROGRAM_CL=$(getcl "program/${PROG}" "${TEMPDIR}/upload.output")
+fi
+#
+# Now walk through the projects and regenerate the configs.
+# Create and upload a CL and capture the CL number and project directory
+# if the project has changed.
+#
+PROJ_CLS=()
+PROJ_DIRS=()
+for PDIR in "${SRC}/project/${PROG}"/*; do
+ PROJ=$(basename "${PDIR}")
+ cd "${PDIR}" || abort "Missing directory: ${PDIR}"
+ branch
+ ./config.star || abort "Generate config failed for ${PROJ}"
+ check_config > "${TEMPDIR}/check_config-${PROJ}.output" || abort "check_config failed for ${PROJ}"
+ #
+ # Check if any files changed.
+ #
+ if changed; then
+ git add .
+ git commit -F - <<EOF
+${PROJ}: Update firmware to ${RELEASE}
+BUG=none
+TEST=FAFT tests on ${PROG}
+EOF
+ repo upload -y "--ht=${BRANCH}" --cbr . > "${TEMPDIR}/upload.${PROJ}.output" 2>&1
+ P_CL=$(getcl "project/${PROG}/${PROJ}" "${TEMPDIR}/upload.${PROJ}.output")
+ PROJ_CLS+=("${P_CL}")
+ PROJ_DIRS+=("${PDIR}")
+ fi
+done
+#
+# Create a Cq-Depend line with all the project CLs
+#
+if [[ -n "${PROJ_CLS[*]}" ]];then
+ SEP=" "
+ PROG_CQD="Cq-Depend:"
+ for CL in "${PROJ_CLS[@]}"; do
+ PROG_CQD="${PROG_CQD}${SEP}chrome-internal:${CL}"
+ SEP=", "
+ done
+ #
+ # Add the Cq-Depend line to the program CL commit message.
+ #
+ cd "${PROGDIR}" || abort "Missing directory: ${PROGDIR}"
+ amend "${PROG_CQD}"
+ repo upload --cbr .
+fi
+#
+# All the boxster configs have been uploaded.
+# Now run the update script and update the firmware manifest.
+#
+# Build base coreboot files
+# TODO: Should be selective here.
+#
+echo "Running emerge for coreboot. This may take a while..."
+if ! ("emerge-${PROG}" --quiet-build chromeos-ec coreboot depthcharge vboot_reference \
+ libpayload chromeos-bootimage coreboot-private-files \
+ "coreboot-private-files-${PROG}"); then
+ abort "emerge for coreboot failed!"
+fi
+echo "emerge of coreboot successful"
+OVERLAY="${SRC}/private-overlays/overlay-${PROG}-private/chromeos-base/chromeos-firmware-${PROG}"
+EB9999="chromeos-firmware-${PROG}-9999.ebuild"
+#
+# Remove any previous attempts to build the firmware.
+#
+"cros_workon-${PROG}" stop "chromeos-base/chromeos-firmware-${PROG}"
+"cros_workon-${PROG}" stop "chromeos-base/chromeos-config-bsp-${PROG}-private"
+cd "${OVERLAY}" || abort "Missing directory: ${OVERLAY}"
+branch
+cd "${SRC}/platform/dev/contrib" || abort "Missing directory: ${SRC}/platform/dev/contrib"
+if ! (./cros_update_firmware -q "--board=${PROG}"); then
+ abort "cros_update_firmware failed for ${PROG}"
+ exit 1
+fi
+cd "${OVERLAY}" || abort "Missing directory: ${OVERLAY}"
+#
+# If files have been updated, then create a CL for the changes.
+#
+OVERLAY_CL=""
+if changed; then
+ #
+ # Bump the version in the ebuild file. Relies on the format
+ # of the version so that the last number is at the end of the line.
+ #
+ CURVERS=$(grep "VERSION=REVBUMP" "${EB9999}" | grep -o "[0-9][0-9]*$")
+ NEXTVERS=$((CURVERS + 1))
+ sed "/VERSION=REVBUMP/s/${CURVERS}$/${NEXTVERS}/" "${EB9999}" > "${TEMPDIR}/new-${EB9999}"
+ cp "${TEMPDIR}/new-${EB9999}" "${EB9999}"
+ git add .
+ git commit -F - <<EOF
+${PROG}: Update firmware to ${RELEASE}
+
+BUG=none
+TEST=FAFT tests on ${PROG}
+
+${CQD}
+EOF
+ #
+ # Upload with no-verify since the file lines are too long.
+ #
+ repo upload "--ht=${BRANCH}" -y --no-verify --cbr . > "${TEMPDIR}/overlay.output" 2>&1
+ OVERLAY_CL=$(getcl "overlays/overlay-${PROG}-private" "${TEMPDIR}/overlay.output")
+ #
+ # Go back and amend all the project commit messages with a Cq-Depend on
+ # the program and overlay CLs.
+ #
+ CQD="Cq-Depend: chrome-internal:${OVERLAY_CL}"
+ if [[ -n "${PROGRAM_CL}" ]]; then
+ CQD="${CQD}, chrome-internal:${PROGRAM_CL}"
+ fi
+ for DIR in "${PROJ_DIRS[@]}"; do
+ cd "${DIR}" || abort "Missing directory: ${DIR}"
+ amend "${CQD}"
+ repo upload --cbr .
+ done
+fi
+#
+# Send all of the CLs to the CQ for a dry run.
+#
+ALL_CLS=$(gerrit -i --raw search "owner:me status:open hashtag:${BRANCH}")
+if [[ -z "${ALL_CLS}" ]]; then
+ echo "No changes required for program ${PROG}"
+ repo abandon "${BRANCH}"
+ exit 0
+fi
+for cl in ${ALL_CLS}; do
+ gerrit -i label-cq "${cl}" 1
+ gerrit -i label-v "${cl}" 1
+ gerrit -i label-as "${cl}" 1
+done
+#
+# If reviewer is set, then add them to the CLs
+#
+if [[ -n "${REVIEWER}" ]]; then
+ echo "Sending CLs ${ALL_CLS} to ${REVIEWER} for review"
+ for cl in ${ALL_CLS}; do
+ gerrit -i reviewers "${cl}" "${REVIEWER}"
+ done
+else
+ echo "Send CLs for review by running:"
+ echo " for cl in ${ALL_CLS}; do gerrit -i reviewers \$cl <reviewer>; done"
+fi
+#
+# Final instructions.
+#
+echo "Run:"
+echo " /build/${PROG}/usr/sbin/chromeos-firmwareupdate --manifest"
+echo "to verify firmware update"
+echo "When submitted, cleanup by running:"
+echo "repo abandon ${BRANCH}"