Blob Blame History Raw
#!/bin/bash
# Copyright (c) 2018 Nicolas Mailhot <nim@fedoraproject.org>
# This file is distributed under the terms of GNU GPL license version 3, or
# any later version.

usage() {
cat >&2 << EOF_USAGE
Usage: $0 <action> [ [-h] ]
                   [ [-p <prefix>] [-g <go path>] ]
                   [ [-v <version>] ] [ [-a <attribute>] ]

<action>             should be one of: provides, requires

Most actions accept the same set of arguments, and will silently ignore those
that do not apply to a specific action. Unless specified otherwise, all
arguments are optional.

“provides”-specific arguments:

-v <version string>: tag the provides with <version string>
-a <attribute>:      an attribute to add to the provides, for example
                     -a "(commit=XXXX)"
                     -a "(branch=YYYY)"
                     -a "(tag=rx.y.z-alpha1)"
                     can be specified several times

“requires”-specific arguments:

-v <version string>: tag symbolink link target requires with <version string>

Common arguments:

-h                   print this help
-p <prefix>:         an optionnal prefix path such as %{buildroot}
-g <go path>:        the root of the Go source tree
                     default value if not set: /usr/share/gocode
EOF_USAGE
exit 1
}

action=''
version=''
prefix=''
gopath=/usr/share/gocode
declare -A attributes

if [[ $# -eq 0 ]] ; then
  usage
else case $1 in
    provides|requires) action=$1 ;;
    *)                 usage ;;
  esac
fi

shift

if ! options=$(getopt -n $0 -o hp:gv:a: \
                      -l help,prefix:,go-path: \
                      -l version:,attribute: \
                      -- "$@")
then
    usage
fi

eval set -- "$options"

while [ $# -gt 0 ] ; do
  case $1 in
    -h|--help)                      usage ;;
    -p|--prefix)                    prefix=$(realpath -sm "$2")  ; shift;;
    -g|--go-path)                   gopath="$2"                  ; shift;;
    -v|--version)                   version="$2"                 ; shift;;
    -a|--attribute)                 IFS=')' read -r -a newattrs <<< "$2"
                                      for index in "${!newattrs[@]}" ; do
                                        newattrs[index]=${newattrs[index]#\(}
                                        attributes[${newattrs[index]%%=*}]=${newattrs[index]#*=}
                                    done                         ; shift;;
    (--)          shift; break;;
    (-*)          usage ;;
    (*)           break;;
  esac
  shift
done

deco=( "" )
for key in "${!attributes[@]}"; do
  [ -n "${attributes[$key]}" ] && deco+=( "($key=${attributes[$key]})" )
done


# Convert paths within gopath to version-constrained provides
provides() {
 local package="${1#${prefix}${gopath}/src/}"
 for index in "${!deco[@]}" ; do
   echo "golang(${package})${deco[index]}${version:+ = ${version}}"
 done
}

# Convert paths within gopath to version-constrained requires
requires() {
  local package="${1#${prefix}${gopath}/src/}"
  echo "golang(${package})${version:+ = ${version}}"
}

# Resolve a symlink target in presence of a build root
resolvelink() {
  local lt=$(realpath -m "$1")
  echo "${prefix}${lt#${prefix}}"
}

# Resolve a symlink to its ultimate target in presence of a build root
ultimateresolvelink() {
  local lt="$1"
  until [[ ! -L ${lt} ]] ; do
    lt=$(resolvelink "${lt}")
  done
  echo "${lt}"
}

# Test if a path is a directory within the target gopath
isgopathdir() {
  local lt="$1"
  if [[ -d ${lt} ]] && [[ "${lt}"/ == "${prefix}${gopath}"/src/* ]] ; then
    true
  else
    false
  fi
}

# A symlink can point to a whole directory tree, but go.attr will only
# trigger on the root symlink.
# Therefore, check the symlink points within the processed import path, then
# walk all the target tree to generate symlink provides/requires
#
# To process nested symlinks the function needs to be provided a working path
# to the symlink tip within the build root as second argument.
processlink() {
  local link="$1"
  local nexttarget=$(resolvelink "$2")
  local linktarget=$(ultimateresolvelink "${nexttarget}")
  if isgopathdir "${linktarget}" ; then
    case ${action} in
      provides) find "${linktarget}" -type d -print | while read subdir ; do
                  provides    "${link}${subdir#${linktarget}}"
                done
                find "${linktarget}" -type l -print | while read sublink ; do
                  processlink "${link}${sublink#${linktarget}}" "${sublink}"
                done ;;
      requires) # Requires is not recursive — it relies on providers requiring their own needs
                find "${nexttarget}" -type d -print | while read subdir ; do
                  requires   "${subdir}"
                done
                find "${nexttarget}" -type l -print | while read sublink ; do
                  if isgopathdir $(ultimateresolvelink "${sublink}") ; then
                  # The owner of the link will declare the correct requires
                    requires "${sublink}"
                  fi
                done ;;
    esac
  fi
}

# go.attr ensures that every time a package declares owning a symlink under
# %{gopath}/src, symlink name will be piped to this script to compute the
# package Go provides/requires.
#
# For legacy reason the script is supposed to be able to handle multiple
# inputs, even though modern rpm invokes it separately for each directory.
while read dir ; do
  if [[ -L $dir ]] ; then
    processlink "$dir" "$dir"
  fi
done | sort -u | grep -v '/\([._]\)\?\(internal\|testdata\|vendor\)\([/)]\)'