#!/bin/bash
#
# Copyright 2012 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Generate version information for the EC binary

# Use this symbol as a separator to be able to reliably concatenate strings of
# text.
dc=$'\001'

# Default marker to indicate 'dirty' repositories
dirty_marker='+'

# This function examines the state of the current directory and attempts to
# extract its version information: the latest tag, if any, how many patches
# are there since the latest tag, the top sha1, and if there are local
# modifications.
#
# Local modifications are reported by concatenating the revision string and
# the string '-dirty' using the $dc symbol as the separator.
#
# If there is no tags defined in this git repository, the base version is
# considered to be 0.0.
#
# If current directory is not a git depository, this function prints out
# "no_version"

get_tree_version() {
  local marker
  local ghash
  local numcommits
  local tag
  local vbase
  local ver_branch
  local ver_major

  if ghash=`git rev-parse --short --verify HEAD 2>/dev/null`; then
    if gdesc=`git describe --dirty --match='v*' 2>/dev/null`; then
      IFS="-" fields=($gdesc)
      tag="${fields[0]}"
      IFS="." vernum=($tag)
      numcommits=$((${vernum[2]}+${fields[1]:-0}))
      ver_major="${vernum[0]}"
      ver_branch="${vernum[1]}"
    else
      numcommits=`git rev-list HEAD | wc -l`
      ver_major="v0"
      ver_branch="0"
    fi
    # avoid putting the -dirty attribute if only the timestamp
    # changed
    git status > /dev/null 2>&1

    if [ -n "$(git diff-index --name-only HEAD 2>/dev/null)" ]; then
      marker="${dirty_marker}"
    else
      marker="-"
    fi
    vbase="${ver_major}.${ver_branch}.${numcommits}${marker}${ghash}"
  else
    # Fall back to the VCSID provided by the packaging system if available.
    if ghash=${VCSID##*-}; then
      vbase="1.1.9999-${ghash:0:7}"
    else
      # then ultimately fails to "no_version"
      vbase="no_version"
    fi
  fi
  if [[ "${marker}" == "${dirty_marker}" ]]; then
      echo "${vbase}${dc}${marker}"
  else
      echo "${vbase}${dc}"
  fi
}


main() {
  local component
  local dir_list
  local gitdate
  local most_recents
  local most_recent_file
  local root
  local timestamp
  local tool_ver
  local values
  local vbase
  local ver

  IFS="${dc}"
  ver="${CR50_DEV:+DBG/}${CRYPTO_TEST:+CT/}${BOARD}_"
  tool_ver=""
  most_recents=()    # Non empty if any of the component repos is 'dirty'.
  dir_list=( . )   # list of component directories, always includes the EC tree

  if [[ -n ${BOARD} ]]; then
    case "${BOARD}" in
      (cr50)
        dir_list+=( ../../third_party/tpm2 )
        dir_list+=( ../pinweaver )
        ;;
      (*_fp)
        dir_list+=( ./private )
        ;;
      (*)
        # For private-crX boards add their git root and cryptoc.
        for root in private-cr5*; do
          if [[ -d "${root}/board/${BOARD}" ]]; then
            dir_list+=( "${root}" ../../third_party/cryptoc )
          fi
        done
        ;;
    esac
  fi
  # Create a combined version string for all component directories.
  for git_dir in ${dir_list[@]}; do
    pushd "${git_dir}" > /dev/null
    component="$(basename "${git_dir}")"
    values=( $(get_tree_version) )
    vbase="${values[0]}"             # Retrieved version information.
    if [[ -n "${values[1]}" ]]; then
      # From each modified repo get the most recently modified file.
      most_recent_file="$(git status --porcelain | \
                                       awk '$1 ~ /[M|A|?]/ {print $2}' |  \
                                       xargs ls -t | head -1)"
      most_recents+=("$(realpath "${most_recent_file}")")
    fi
    if [ "${component}" != "." ]; then
      ver+=" ${component}:"
    fi
    ver+="${vbase}"
    tool_ver+="${vbase}"
    popd > /dev/null
  done

  # On some boards where the version number consists of multiple components we
  # want to separate the first word of the version string as the version of the
  # EC tree.
  IFS=' ' first_word=(${ver})

  echo "/* This file is generated by util/getversion.sh */"

  echo "/* Version string, truncated to 31 chars (+ terminating null = 32) */"
  echo "#define CROS_EC_VERSION32 \"${first_word:0:31}\""

  echo "/* Version string for ectool. */"
  echo "#define CROS_ECTOOL_VERSION \"${tool_ver}\""

  echo "/* Version string for stm32mon. */"
  echo "#define CROS_STM32MON_VERSION \"${tool_ver}\""

  echo "/* Sub-fields for use in Makefile.rules and to form build info string"
  echo " * in common/version.c. */"
  echo "#define VERSION \"${ver}\""
  if [ "$REPRODUCIBLE_BUILD" = 1 ]; then
    echo '#define BUILDER "reproducible@build"'
  else
    echo "#define BUILDER \"${USER}@`hostname`\""
  fi

  if [[ ${#most_recents[@]} != 0 ]]; then
    # There are modified files, use the timestamp of the most recent one as
    # the build version timestamp.
    most_recent_file="$(ls -t "${most_recents[@]}" | head -1)"
    timestamp="$(stat -c '%y' "${most_recent_file}" | sed 's/\..*//')"
    echo "/* Repo is dirty, using time of most recent file modification. */"
    echo "#define DATE \"${timestamp}\""
  else
    echo "/* Repo is clean, use the commit date of the last commit. */"
    # If called from an ebuild we won't have a git repo, so redirect stderr
    # to avoid annoying 'Not a git repository' errors.
    gitdate="$(
      for git_dir in "${dir_list[@]}"; do
        git -C "${git_dir}" log -1 --format='%ct %ci' HEAD 2>/dev/null
      done | sort | tail -1 | cut -d ' ' -f '2 3')"
    echo "#define DATE \"${gitdate}\""
  fi
}

main
