golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.54k stars 17.6k forks source link

cmd/go: go run package@version vargs... from module directory and outside it #49720

Closed cardil closed 2 years ago

cardil commented 2 years ago

Proposal

To allow executing Go tools without the need to install them. This is similar to how npx is operating. For example, using npx, you can invoke the following command, and npx will fetch snyk package and run it passing args:

$ npx snyk@v1 test --print-deps

I think similar behavior could be added to go run command. It already allows executing packages that are part of our GOPATH. When a given package isn't part of our module (or we are not within the module), we can create an ad-hoc module in user home directory, install the requested package there, and run it in the original directory for user profit.

$ go run github.com/cli/cli/v2/cmd/gh@v2 pr list --repo knative-sandbox/kn-plugin-event

What did you do?

To achieve that behavior with current Go, I use the following Bash functions:

# go.get-tool will 'go get' any package $2 and install it to $1. Replaces, can
# be set with $GO_GETTOOL_REPLACES.
function go.get-tool {
  local tmp_dir
  if ! [ -f "${1}" ]; then
    tmp_dir="$(mktemp -d)"
    # shellcheck disable=SC2064
    trap "rm -rf ${tmp_dir}" EXIT
    pushd "$tmp_dir" >/dev/null 2>&1
    go mod init tmp
    if [ -n "${GO_GETTOOL_REPLACES:-}" ]; then
      go mod edit -replace "${GO_GETTOOL_REPLACES}"
    fi
    echo "Downloading ${2}"
    GOBIN="$(dirname "${1}")" go get "${2}"
    popd >/dev/null 2>&1
  fi
  echo "Using ${2} in ${1}"
}

# go.run-tool will 'go get' package $1 and install it to temp path. Replaces, 
# can be set with $GO_GETTOOL_REPLACES. You could pass arguments to the tool by
# setting $2 and up.
function go.run-tool {
  local path_id systmp_dir bin_dir package package_id bin
  package="${1:?'Pass a GO executable package ref as arg[1]'}"
  shift
  path_id="$(echo "${BASH_SOURCE[0]:-$0}" | sha1sum - | awk '{print $1}')"
  package_id="$(echo "$package" | sha1sum - | awk '{print $1}')"
  systmp_dir="$(dirname "$(mktemp -d -u)")"
  bin_dir="${systmp_dir}/go-run-tool/${path_id}/${package_id}"
  mkdir -p "$bin_dir"
  bin="${package%@*}"
  bin="${bin_dir}/${bin##*/}"

  go.get-tool "$bin" "$package"

  "$bin" "$@"
}

By having those, I could execute command like:

$ go.run-tool \
    github.com/cli/cli/v2/cmd/gh@v2.2.1-0.20211122114001-a3940020f938 \
    pr list

What did you expect to see?

I'd like to have my Go tool fetched and executed, just as it does with my script wrapper go.run-tool.

What did you see instead?

package github.com/cli/cli/v2/cmd/gh@v2.2.1-0.20211122114001-a3940020f938: can only use path@version syntax with 'go get' and 'go install' in module-aware mode

or when outside of module

no required module provides package github.com/cli/cli/v2/cmd/gh: working directory is not part of a module
seankhliao commented 2 years ago

This is available in 1.17+