Blob Blame Raw
#!/bin/bash

usage() {
cat >&2 << EOF_USAGE
Usage: $0 -g <go import path> [-p <prefix>] [-G <go path>] [-v <version string>]
<go import path>   the Go import path of the target package
<prefix>:          an eventual prefix path such as %{buildroot}
<go path>:         the root of the Go source tree
<version string>:  tag symlinked renamings with <version string>

Example:
echo filename | $0 -g golang.org/x/net -G /usr/share/gocode
EOF_USAGE
exit 1
}

if ! options=$(getopt -n $0 -o hg:p:G:v: -l help,goipath:,prefix:,gopath:,version: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 2
fi

eval set -- "$options"

# Convert paths within gopath to version-constrained requires
# Never call it before checking the path belongs to the package import path
requires() {
  local package="${1#${root}/src/}"
  echo "golang(${package})${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 package import path.
isgoipathdir() {
  local lt="$1"
  if [[ -d ${lt} ]] && [[ "${lt}"/ == "${root}/src/${goipath}"/* ]] ; then
    true
  else
    false
  fi
}

# Test if a path is vendored
notvendored() {
  local p=$(realpath -sm "$1")
  local vr="${p}/"
  vr="${vr%/vendor/*}"
  if [[ "${vr}" != "${p}/" ]] ; then
    false
  else
    true
  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 requires
#
# Requires is not recursive — it relies on providers requiring their own needs
processlink() {
  local link="$1"
  local nexttarget=$(resolvelink "$1")
  local linktarget=$(ultimateresolvelink "${nexttarget}")
  if ( isgoipathdir "${linktarget}" ) && ( notvendored "${linktarget}" ) ; then
    find "${nexttarget}" -type d -print | while read subdir ; do
      requires   "${subdir}"
    done
    find "${nexttarget}" -type l -print | while read sublink ; do
      if isgoipathdir $(ultimateresolvelink "${sublink}") ; then
        # The owner of the link will declare the correct requires
        requires "${sublink}"
      fi
    done
  fi
}

prefix=''
gopath=/usr/share/gocode

while [ $# -gt 0 ] ; do
  case $1 in
    -h|--help)    usage ;;
    -g|--goipath) goipath="$2" ; shift;;
    -p|--prefix)  prefix=$(realpath -sm "$2") ; shift;;
    -G|--gopath)  gopath="$2" ; shift;;
    -v|--version) version=" = $2" ; shift;;
    (--)          shift; break;;
    (-*)          echo "$0: error - unrecognized option $1" >&2; exit 3;;
    (*)           break;;
  esac
  shift
done

# Detection of the package import path root is necessary when processing
# symlinks. We can only be sure the requires of a symlink target are correctly
# computed, if it is processed here as part of the package import path tree.
#
# When go dep stabilizes and can be generalized import path root detection may
# be automated by searching for the first level of go dep lock files.
#
# However the requirement to set %{goipath} may remain to make sure we avoid
# processing directories created without applying the sanity checks documented
# in Fedora Packaging Guidelines.
[[ -z ${goipath} ]] && exit 0

root="${prefix}${gopath}"

# go.attr ensures that every time a package declares owning a directory or
# symlink under %{gopath}/src, the directory or symlink name will be piped to
# this script to compute the package Go requires.
#
# When go dep stabilizes and can be generalized it will be possible to simplify
# requires computation by running go dep on the import path root only. go dep
# will also make possible to compute requires version constrains. However,
# there will still be a need to trigger require computation on every symlink.
#
# 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"
  else
    if [[ -d $dir ]] ; then
      # When triggered on a directory, make sure it is within the import path,
      # and not vendored, run go list to get the directory import, then filter
      # out standard Go imports provided by golang itself.
      # Invoking go is awfully slow. You *will* fear rpm has hung the first
      # time that happens.
      # Hopefully go dep will replace this with something faster soonish.
      dir=$(realpath -sm "$dir")
      if notvendored "$dir" ; then
        package="${dir#${root}/src/}"
        if $(ls ${dir}/*.go >/dev/null 2>&1) ; then
          GOPATH=$root go list -e -f \
                '{{ join .Imports "\n" }}' "$package" |\
            while read import ; do
              GOPATH=$root go list -e -f \
                '{{if not .Standard}}{{.ImportPath | printf "golang(%s)\n"}}{{end}}' "$import"
            done
        fi
      fi
    fi
  fi
done | sort | uniq \
     | grep -v '\(/\([._]\)\?\(internal\|testdata\|vendor\)\([/)]\)\|golang(C)\)'