Parse dependency versions from TF-M CMake

To make sure CI totally aligned with TF-M, it is better to automatically
parse dependency versions by CI instead of passing a fixed version via
Jenkins environment variable.

This patch adds the logic to select dependency version specified in TF-M
CMake system by default. And CI user can also manually change the
dependency version by setting the corresponding Jenkins environment
variable when triggerring CI jobs.

For the dependency version which differs from platforms, for example
tf-m-extras, the version is double checked with the value in CMake Cache
in each single build-config job.

Signed-off-by: Xinyu Zhang <xinyu.zhang@arm.com>
Change-Id: Ia528783f4235786530c7f25785facae93534c1d3
diff --git a/clone.sh b/clone.sh
index 407883d..b5b3966 100755
--- a/clone.sh
+++ b/clone.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 #
-# Copyright (c) 2021-2022 Arm Limited. All rights reserved.
+# Copyright (c) 2021-2023 Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -29,59 +29,7 @@
 
 set -e
 
-# Global defaults
-GIT_CLONE_PARAMS="--no-checkout"
-
-# Must projects
-TFM_PROJECT="${CODE_REPO:?}"
-TFM_REFSPEC="${GERRIT_REFSPEC:?}"
-TFM_NAME="trusted-firmware-m"
-
-SCRIPTS_PROJECT="${CI_SCRIPTS_REPO:?}"
-SCRIPTS_REFSPEC="${CI_SCRIPTS_BRANCH:?}"
-SCRIPTS_NAME="tf-m-ci-scripts"
-
-# Optional projects
-TFM_TESTS_PROJECT="${TFM_TESTS_URL:-}"
-TFM_TESTS_REFSPEC="${TFM_TESTS_REFSPEC:-}"
-TFM_TESTS_NAME="tf-m-tests"
-
-MBEDTLS_PROJECT="${MBEDTLS_URL:-}"
-MBEDTLS_REFSPEC="${MBEDTLS_VERSION:-}"
-MBEDTLS_NAME="mbedtls"
-
-MCUBOOT_PROJECT="${MCUBOOT_URL:-}"
-MCUBOOT_REFSPEC="${MCUBOOT_REFSPEC:-}"
-MCUBOOT_NAME="mcuboot"
-
-PSA_ARCH_TESTS_PROJECT="${PSA_ARCH_TESTS_URL:-}"
-PSA_ARCH_TESTS_REFSPEC="${PSA_ARCH_TESTS_VERSION:-}"
-PSA_ARCH_TESTS_NAME="psa-arch-tests"
-
-QCBOR_PROJECT="${QCBOR_URL:-}"
-QCBOR_REFSPEC="${QCBOR_VERSION:-}"
-QCBOR_NAME="qcbor"
-
-TFM_EXTRAS_PROJECT="${TFM_EXTRAS_URL:-}"
-TFM_EXTRAS_REFSPEC="${TFM_EXTRAS_REFSPEC:-}"
-TFM_EXTRAS_NAME="tf-m-extras"
-
-QA_TOOLS_PROJECT="https://review.trustedfirmware.org/ci/qa-tools"
-QA_TOOLS_REFSPEC="openci"
-QA_TOOLS_NAME="qa-tools"
-
-# Array containing "<repo url>;"<repo name>;<refspec>" elements
-repos=(
-    "${TFM_PROJECT};${TFM_NAME};${TFM_REFSPEC}"
-    "${TFM_TESTS_PROJECT};${TFM_TESTS_NAME};${TFM_TESTS_REFSPEC}"
-    "${SCRIPTS_PROJECT};${SCRIPTS_NAME};${SCRIPTS_REFSPEC}"
-    "${MBEDTLS_PROJECT};${MBEDTLS_NAME};${MBEDTLS_REFSPEC}"
-    "${MCUBOOT_PROJECT};${MCUBOOT_NAME};${MCUBOOT_REFSPEC}"
-    "${PSA_ARCH_TESTS_PROJECT};${PSA_ARCH_TESTS_NAME};${PSA_ARCH_TESTS_REFSPEC}"
-    "${QCBOR_PROJECT};${QCBOR_NAME};${QCBOR_REFSPEC}"
-    "${TFM_EXTRAS_PROJECT};${TFM_EXTRAS_NAME};${TFM_EXTRAS_REFSPEC}"
-    "${QA_TOOLS_PROJECT};${QA_TOOLS_NAME};${QA_TOOLS_REFSPEC}"
-)
+. $(dirname $0)/util_git.sh
 
 # Take into consideration non-CI runs where SHARE_FOLDER variable
 # may not be present
@@ -96,43 +44,112 @@
 echo "Share Folder path: ${SHARE_FOLDER}"
 echo
 
-# Don't print mouthfull "You are in 'detached HEAD' state." messages.
-git config --global advice.detachedHead false
+# Parse dependency version specified in TF-M CMake configs
+function parse_version() {
+    CONFIG_FILE_NAME=$1
+    DEPENDENCY_NAME=$2
+    CONFIG_FILE_PATH="${SHARE_FOLDER}/${TFM_NAME}/${CONFIG_FILE_NAME}"
 
-# clone git repos
-for repo in ${repos[@]}; do
+    VERSION="$(grep "set(${DEPENDENCY_NAME}" ${CONFIG_FILE_PATH} | cut -d\" -f2)"
 
-    # parse the repo elements
+    if [ -z "${VERSION}" ]; then
+        VERSION="refs/heads/master"
+    fi
+
+    echo "${VERSION}"
+}
+
+# Must projects
+TFM_PROJECT="${CODE_REPO:?}"
+TFM_REFSPEC="${GERRIT_REFSPEC:?}"
+TFM_NAME="trusted-firmware-m"
+
+SCRIPTS_PROJECT="${CI_SCRIPTS_REPO:?}"
+SCRIPTS_REFSPEC="${CI_SCRIPTS_BRANCH:?}"
+SCRIPTS_NAME="tf-m-ci-scripts"
+
+# Array containing "<repo url>;"<repo name>;<refspec>" elements
+must_repos=(
+    "${TFM_PROJECT};${TFM_NAME};${TFM_REFSPEC}"
+    "${SCRIPTS_PROJECT};${SCRIPTS_NAME};${SCRIPTS_REFSPEC}"
+)
+
+for repo in ${must_repos[@]}; do
+    # Parse the repo elements
     REPO_URL="$(echo "${repo}" | awk -F ';' '{print $1}')"
     REPO_NAME="$(echo "${repo}" | awk -F ';' '{print $2}')"
     REPO_REFSPEC="$(echo "${repo}" | awk -F ';' '{print $3}')"
 
-    # in case repository is not define, just skip it
-    if [ -z "${REPO_URL}" ]; then
-        continue
-    fi
-
-    # clone and checkout in case it does not exit
-    if [ ! -d ${SHARE_FOLDER}/${REPO_NAME} ]; then
-        git clone --quiet ${GIT_CLONE_PARAMS} ${REPO_URL} ${SHARE_FOLDER}/${REPO_NAME}
-
-        # fetch and checkout the corresponding refspec
-        cd ${SHARE_FOLDER}/${REPO_NAME}
-
-        git fetch ${REPO_URL} ${REPO_REFSPEC}
-        git checkout FETCH_HEAD
-        echo -e "Share Folder ${SHARE_FOLDER}/${REPO_NAME} $(git rev-parse --short HEAD)\n"
-        cd $OLDPWD
-
+    if [ ! -d "${SHARE_FOLDER}/${REPO_NAME}" ]; then
+        git_clone $REPO_URL "${SHARE_FOLDER}/${REPO_NAME}"
+        git_checkout "${SHARE_FOLDER}/${REPO_NAME}" $REPO_REFSPEC
     else
-        # otherwise just show the head's log
-        cd ${SHARE_FOLDER}/${REPO_NAME}
-        echo -e "Share Folder ${SHARE_FOLDER}/${REPO_NAME} $(git rev-parse --short HEAD)\n"
+        cd "${SHARE_FOLDER}/${REPO_NAME}"
+        echo -e "Share Folder ${REPO_NAME} $(git rev-parse --short HEAD)\n"
         cd $OLDPWD
     fi
 
-    # copy repository into pwd dir (workspace in CI), so each job would work
+    # Copy repos into pwd dir (workspace in CI), so each job would work
     # on its own workspace
-    cp -a -f ${SHARE_FOLDER}/${REPO_NAME} ${WORKSPACE}/${REPO_NAME}
+    cp -a -f "${SHARE_FOLDER}/${REPO_NAME}" "${WORKSPACE}/${REPO_NAME}"
+done
 
+# Dependency projects
+TFM_TESTS_PROJECT="${TFM_TESTS_URL:-}"
+TFM_TESTS_REFSPEC="${TFM_TESTS_REFSPEC:-"$(parse_version lib/ext/tf-m-tests/repo_config_default.cmake TFM_TEST_REPO_VERSION)"}"
+TFM_TESTS_NAME="tf-m-tests"
+
+MBEDTLS_PROJECT="${MBEDTLS_URL:-}"
+MBEDTLS_REFSPEC="${MBEDTLS_VERSION:-"$(parse_version config/config_base.cmake MBEDCRYPTO_VERSION)"}"
+MBEDTLS_NAME="mbedtls"
+
+MCUBOOT_PROJECT="${MCUBOOT_URL:-}"
+MCUBOOT_REFSPEC="${MCUBOOT_REFSPEC:-"$(parse_version config/config_base.cmake MCUBOOT_VERSION)"}"
+MCUBOOT_NAME="mcuboot"
+
+PSA_ARCH_TESTS_PROJECT="${PSA_ARCH_TESTS_URL:-}"
+PSA_ARCH_TESTS_REFSPEC="${PSA_ARCH_TESTS_VERSION:-"$(parse_version config/config_base.cmake PSA_ARCH_TESTS_VERSION)"}"
+PSA_ARCH_TESTS_NAME="psa-arch-tests"
+
+QCBOR_PROJECT="${QCBOR_URL:-}"
+QCBOR_REFSPEC="${QCBOR_VERSION:-"$(parse_version lib/ext/qcbor/CMakeLists.txt QCBOR_VERSION)"}"
+QCBOR_NAME="qcbor"
+
+TFM_EXTRAS_PROJECT="${TFM_EXTRAS_URL:-}"
+TFM_EXTRAS_REFSPEC="${TFM_EXTRAS_REFSPEC:-"$(parse_version lib/ext/tf-m-extras/CMakeLists.txt TFM_EXTRAS_REPO_VERSION)"}"
+TFM_EXTRAS_NAME="tf-m-extras"
+
+QA_TOOLS_PROJECT="https://review.trustedfirmware.org/ci/qa-tools"
+QA_TOOLS_REFSPEC="openci"
+QA_TOOLS_NAME="qa-tools"
+
+# Array containing "<repo url>;"<repo name>;<refspec>" elements
+dependency_repos=(
+    "${TFM_TESTS_PROJECT};${TFM_TESTS_NAME};${TFM_TESTS_REFSPEC}"
+    "${MBEDTLS_PROJECT};${MBEDTLS_NAME};${MBEDTLS_REFSPEC}"
+    "${MCUBOOT_PROJECT};${MCUBOOT_NAME};${MCUBOOT_REFSPEC}"
+    "${PSA_ARCH_TESTS_PROJECT};${PSA_ARCH_TESTS_NAME};${PSA_ARCH_TESTS_REFSPEC}"
+    "${QCBOR_PROJECT};${QCBOR_NAME};${QCBOR_REFSPEC}"
+    "${TFM_EXTRAS_PROJECT};${TFM_EXTRAS_NAME};${TFM_EXTRAS_REFSPEC}"
+    "${QA_TOOLS_PROJECT};${QA_TOOLS_NAME};${QA_TOOLS_REFSPEC}"
+)
+
+for repo in ${dependency_repos[@]}; do
+    # Parse the repo elements
+    REPO_URL="$(echo "${repo}" | awk -F ';' '{print $1}')"
+    REPO_NAME="$(echo "${repo}" | awk -F ';' '{print $2}')"
+    REPO_REFSPEC="$(echo "${repo}" | awk -F ';' '{print $3}')"
+
+    if [ ! -d "${SHARE_FOLDER}/${REPO_NAME}" ]; then
+        git_clone $REPO_URL "${SHARE_FOLDER}/${REPO_NAME}"
+        git_checkout "${SHARE_FOLDER}/${REPO_NAME}" $REPO_REFSPEC
+    else
+        cd "${SHARE_FOLDER}/${REPO_NAME}"
+        echo -e "Share Folder ${REPO_NAME} $(git rev-parse --short HEAD)\n"
+        cd $OLDPWD
+    fi
+
+    # Copy repos into pwd dir (workspace in CI), so each job would work
+    # on its own workspace
+    cp -a -f "${SHARE_FOLDER}/${REPO_NAME}" "${WORKSPACE}/${REPO_NAME}"
 done
diff --git a/run-build.sh b/run-build.sh
index f114702..c01c6b3 100755
--- a/run-build.sh
+++ b/run-build.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Copyright (c) 2020-2022, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2020-2023, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -13,6 +13,31 @@
 # Expected to have trusted-firmware-m cloned to same level as this git tree
 #
 
+. $(dirname $0)/util_cmake.sh
+. $(dirname $0)/util_git.sh
+
+# For dependency that differs from platforms, the versions need to be checkded
+# in each single build job.
+function check_dependency_version() {
+    TFM_EXTRAS_PATH="${WORKSPACE}/tf-m-extras"
+    TFM_EXTRAS_REFSPEC="$(get_cmake_cache ${WORKSPACE}/trusted-firmware-m/build TFM_EXTRAS_REPO_VERSION)"
+
+    # Array containing "<repo path>;<refspec>" elements
+    dependency_repos=(
+        "${TFM_EXTRAS_PATH};${TFM_EXTRAS_REFSPEC}"
+    )
+
+    for repo in ${dependency_repos[@]}; do
+        # Parse the repo elements
+        REPO_PATH="$(echo "${repo}" | awk -F ';' '{print $1}')"
+        REPO_REFSPEC="$(echo "${repo}" | awk -F ';' '{print $2}')"
+
+        if [ ! -z "$REPO_REFSPEC" ] ; then
+            git_checkout $REPO_PATH $REPO_REFSPEC
+        fi
+    done
+}
+
 set -ex
 
 if [ -z "$CONFIG_NAME" ] ; then
@@ -49,12 +74,12 @@
 fi
 
 if [ -z "$cmake_config_cmd" ] ; then
-    echo "No CMake config commands found."
+    echo "No CMake config command found."
     exit 1
 fi
 
 if [ -z "$cmake_build_cmd" ] ; then
-    echo "No build image commands found."
+    echo "No CMake build command found."
     exit 1
 fi
 
@@ -80,4 +105,16 @@
 mkdir trusted-firmware-m/build
 cd trusted-firmware-m/build
 
-eval "set -ex ; $cmake_config_cmd; $cmake_build_cmd; $post_build_cmd"
+set +e
+eval $cmake_config_cmd
+cmake_cfg_error=$?
+set -e
+
+check_dependency_version
+
+if [ $cmake_cfg_error != 0 ] ; then
+    rm -rf trusted-firmware-m/build/*
+    eval $cmake_config_cmd
+fi
+
+eval "$cmake_build_cmd; $post_build_cmd"
diff --git a/util_cmake.sh b/util_cmake.sh
index 8895123..00b91c9 100755
--- a/util_cmake.sh
+++ b/util_cmake.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 #-------------------------------------------------------------------------------
-# Copyright (c) 2018-2019, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2018-2023, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -297,3 +297,18 @@
 	done
 	return $error
 }
+
+# Get CMake variable value from cache
+function get_cmake_cache {
+    local build_dir=$1
+    local cmake_var=$2
+
+    if [ ! -d ${REPO_PATH} ]; then
+        return
+    fi
+
+    cd $build_dir
+    cache_value="$(cmake -L 2> /dev/null | grep $cmake_var | awk -F '=' '{print $2}')"
+    echo $cache_value
+    cd $OLDPWD
+}
diff --git a/util_git.sh b/util_git.sh
new file mode 100644
index 0000000..9cbf835
--- /dev/null
+++ b/util_git.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2023 Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+# Don't print mouthful "You are in 'detached HEAD' state." messages.
+git config --global advice.detachedHead false
+
+# Global defaults
+GIT_CLONE_PARAMS="--no-checkout"
+
+function git_clone() {
+    # Parse the repo elements
+    REPO_URL=$1
+    REPO_PATH=$2
+
+    # In case repository is not defined, just skip it
+    if [ -z "${REPO_URL}" ]; then
+        return
+    fi
+
+    # Clone if it does not exit
+    if [ ! -d ${REPO_PATH} ]; then
+        git clone --quiet ${GIT_CLONE_PARAMS} ${REPO_URL} ${REPO_PATH}
+    fi
+}
+
+function git_checkout() {
+    # Parse the repo elements
+    REPO_PATH=$1
+    REPO_REFSPEC=$2
+
+    # Checkout if repo exits
+    if [ -d ${REPO_PATH} ]; then
+        cd ${REPO_PATH}
+
+        # Fetch the corresponding refspec
+        REPO_FETCH_HEAD=$(git ls-remote --quiet | grep ${REPO_REFSPEC} | awk -v d=" " '{s=(NR==1?s:s d)$1} END{print s}')
+
+        if [ -z "${REPO_FETCH_HEAD}" ]; then
+            git fetch --all
+        else
+            git fetch origin ${REPO_FETCH_HEAD}
+        fi
+
+        # Checkout to specified refspec
+        git checkout ${REPO_REFSPEC}
+        echo -e "Share Folder ${REPO_PATH} $(git rev-parse --short HEAD)\n"
+        cd $OLDPWD
+    fi
+}