#!/usr/bin/env bash readonly SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})" readonly SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" readonly SOURCE_DIR="$(dirname "$(dirname "${SCRIPT_DIR}")")" readonly FN_DIR="$(dirname "${SCRIPT_DIR}")/functions" source "${SCRIPT_DIR}/functions.sh" unset CDPATH set -euo pipefail usage() { cat <<-EOF Usage: ${SCRIPT_NAME} [] Description: Installs protoc, various supporting Go tools, and then regenerates all Go files from protobuf definitions. In addition to running the protoc generator it will also fixup build tags in the generated code and regenerate mog outputs and RPC stubs. Options: --protoc-version Version of protoc to install. It defaults to what is specified in the makefile. --tools-only Install all required tools but do not generate outputs. -h | --help Print this help text. EOF } function err_usage { err "$1" err "" err "$(usage)" } function main { local protoc_version= local tools_only= while test $# -gt 0 do case "$1" in -h | --help ) usage return 0 ;; --protoc-version ) protoc_version="$2" shift 2 ;; --tools-only ) tools_only=1 shift ;; esac done if test -z "${protoc_version}" then protoc_version="$(make --no-print-directory print-PROTOC_VERSION)" if test -z "${protoc_version}" then err_usage "ERROR: no proto-version specified and version could not be discovered" return 1 fi fi # ensure the correct protoc compiler is installed protoc_install "${protoc_version}" if test -z "${protoc_bin}" ; then exit 1 fi # ensure these tools are installed proto_tools_install if [[ -n $tools_only ]]; then return 0 fi # Compute some data from dependencies in non-local variables. go mod download golang_proto_path="$(go list -f '{{ .Dir }}' -m github.com/golang/protobuf)" # golang_proto_mod_path="$(sed -e 's,\(.*\)github.com.*,\1,' <<< "${golang_proto_path}")" golang_proto_mod_path="$(go env GOMODCACHE)" declare -a proto_files while IFS= read -r pkg; do pkg="${pkg#"./"}" proto_files+=( "$pkg" ) done < <(find . -name '*.proto' | grep -v 'vendor/' | grep -v '.protobuf' | sort ) for proto_file in "${proto_files[@]}"; do generate_protobuf_code "${proto_file}" done status "Generated all protobuf Go files" generate_mog_code status "Generated all mog Go files" return 0 } # Installs the version of protoc specified by the first argument. # # Will set 'protoc_bin' function protoc_install { local protoc_version="${1:-}" local protoc_os if test -z "${protoc_version}" then protoc_version="$(make --no-print-directory print-PROTOC_VERSION)" if test -z "${protoc_version}" then err "ERROR: no protoc-version specified and version could not be discovered" return 1 fi fi case "$(uname)" in Darwin) protoc_os="osx" ;; Linux) protoc_os="linux" ;; *) err "unexpected OS: $(uname)" return 1 esac local protoc_zip="protoc-${protoc_version}-${protoc_os}-x86_64.zip" local protoc_url="https://github.com/protocolbuffers/protobuf/releases/download/v${protoc_version}/${protoc_zip}" local protoc_root=".protobuf/protoc-${protoc_os}-${protoc_version}" # This is updated for use outside of the function. protoc_bin="${protoc_root}/bin/protoc" if [[ -x "${protoc_bin}" ]]; then status "protocol buffer compiler version already installed: ${protoc_version}" return 0 fi status_stage "installing protocol buffer compiler version: ${protoc_version}" mkdir -p .protobuf/tmp if [[ ! -f .protobuf/tmp/${protoc_zip} ]]; then \ ( cd .protobuf/tmp && curl -sSL "${protoc_url}" -o "${protoc_zip}" ) fi mkdir -p "${protoc_root}" unzip -d "${protoc_root}" ".protobuf/tmp/${protoc_zip}" chmod -R a+Xr "${protoc_root}" chmod +x "${protoc_bin}" return 0 } function proto_tools_install { local protoc_gen_go_version local mog_version local protoc_go_inject_tag_version protoc_gen_go_version="$(grep github.com/golang/protobuf go.mod | awk '{print $2}')" mog_version="$(make --no-print-directory print-MOG_VERSION)" protoc_go_inject_tag_version="$(make --no-print-directory print-PROTOC_GO_INJECT_TAG_VERSION)" # echo "go: ${protoc_gen_go_version}" # echo "mog: ${mog_version}" # echo "tag: ${protoc_go_inject_tag_version}" install_versioned_tool \ 'protoc-gen-go' \ 'github.com/golang/protobuf' \ "${protoc_gen_go_version}" \ 'github.com/golang/protobuf/protoc-gen-go' install_unversioned_tool \ protoc-gen-go-binary \ 'github.com/hashicorp/protoc-gen-go-binary@master' install_versioned_tool \ 'protoc-go-inject-tag' \ 'github.com/favadi/protoc-go-inject-tag' \ "${protoc_go_inject_tag_version}" \ 'github.com/favadi/protoc-go-inject-tag' install_versioned_tool \ 'mog' \ 'github.com/hashicorp/mog' \ "${mog_version}" \ 'github.com/hashicorp/mog' return 0 } function install_unversioned_tool { local command="$1" local install="$2" if ! command -v "${command}" &>/dev/null ; then status_stage "installing tool: ${install}" go install "${install}" else debug "skipping tool: ${install} (installed)" fi return 0 } function install_versioned_tool { local command="$1" local module="$2" local version="$3" local installbase="$4" local should_install= local got local expect="${module}@${version}" local install="${installbase}@${version}" if [[ -z "$version" ]]; then err "cannot install '${command}' no version selected" return 1 fi if [[ "$version" = "@DEV" ]]; then if ! command -v "${command}" &>/dev/null ; then err "dev version of '${command}' requested but not installed" return 1 fi status "skipping tool: ${installbase} (using development version)" return 0 fi if command -v "${command}" &>/dev/null ; then got="$(go version -m $(which "${command}") | grep '\bmod\b' | grep "${module}" | awk '{print $2 "@" $3}')" if [[ "$expect" != "$got" ]]; then should_install=1 fi else should_install=1 fi if [[ -n $should_install ]]; then status_stage "installing tool: ${install}" go install "${install}" else debug "skipping tool: ${install} (installed)" fi return 0 } function generate_protobuf_code { local proto_path="${1:-}" if [[ -z "${proto_path}" ]]; then err "missing protobuf path argument" return 1 fi if [[ -z "${golang_proto_path}" ]]; then err "golang_proto_path was not set" return 1 fi if [[ -z "${golang_proto_mod_path}" ]]; then err "golang_proto_mod_path was not set" return 1 fi local proto_go_path="${proto_path%%.proto}.pb.go" local proto_go_bin_path="${proto_path%%.proto}.pb.binary.go" local proto_go_rpcglue_path="${proto_path%%.proto}.rpcglue.pb.go" local go_proto_out='paths=source_relative,plugins=grpc:' status_stage "Generating ${proto_path} into ${proto_go_path} and ${proto_go_bin_path}" rm -f "${proto_go_path}" ${proto_go_bin_path}" ${proto_go_rpcglue_path}" print_run ${protoc_bin} \ -I="${golang_proto_path}" \ -I="${golang_proto_mod_path}" \ -I="${SOURCE_DIR}" \ --go_out="${go_proto_out}${SOURCE_DIR}" \ --go-binary_out="${SOURCE_DIR}" \ "${proto_path}" || { err "Failed to run protoc for ${proto_path}" return 1 } print_run protoc-go-inject-tag -input="${proto_go_path}" || { err "Failed to run protoc-go-inject-tag for ${proto_path}" return 1 } local build_tags build_tags="$(head -n 2 "${proto_path}" | grep '^//go:build\|// +build' || true)" if test -n "${build_tags}"; then echo -e "${build_tags}\n" >> "${proto_go_bin_path}.new" cat "${proto_go_bin_path}" >> "${proto_go_bin_path}.new" mv "${proto_go_bin_path}.new" "${proto_go_bin_path}" fi # NOTE: this has to run after we fix up the build tags above rm -f "${proto_go_rpcglue_path}" print_run go run ./internal/tools/proto-gen-rpc-glue/main.go -path "${proto_go_path}" || { err "Failed to generate consul rpc glue outputs from ${proto_path}" return 1 } return 0 } function generate_mog_code { local mog_order mog_order="$(go list -tags "${GOTAGS}" -deps ./proto/pb... | grep "consul/proto")" for FULL_PKG in ${mog_order}; do PKG="${FULL_PKG/#github.com\/hashicorp\/consul\/}" status_stage "Generating ${PKG}/*.pb.go into ${PKG}/*.gen.go with mog" find "$PKG" -name '*.gen.go' -delete if [[ -n "${GOTAGS}" ]]; then print_run mog -tags "${GOTAGS}" -source "./${PKG}/*.pb.go" else print_run mog -source "./${PKG}/*.pb.go" fi done return 0 } main "$@" exit $?