#!/usr/bin/env bash

# This script builds multiple Go binaries for Coder with the given OS and
# architecture combinations.
#
# Usage: ./build_go_matrix.sh [--version 1.2.3-devel+abcdef] [--output dist/] [--slim] [--sign-darwin] [--archive] [--package-linux] [--agpl] os1:arch1,arch2 os2:arch1 os1:arch3
#
# If no OS:arch combinations are provided, nothing will happen and no error will
# be returned. Slim builds are disabled by default. If no version is specified,
# defaults to the version from ./version.sh
#
# The --output parameter must be a directory with a trailing slash where all
# files will be dropped with the default name scheme
# `coder_$version_$os_$arch(.exe)?`, or must contain the `{os}` and `{arch}`
# template variables. You may also use `{version}`. Note that for windows builds
# the `.exe` suffix will be appended automatically.
#
# Unless overridden via --output, the built binary will be dropped in
# "$repo_root/dist/coder_$version_$os_$arch" (with a ".exe" suffix for windows
# builds).
#
# If the --sign-darwin parameter is specified, all darwin binaries will be
# signed using the `codesign` utility. $AC_APPLICATION_IDENTITY must be set and
# the signing certificate must be imported for this to work.
#
# If the --archive parameter is specified, all binaries will be archived using
# ./archive.sh. The --sign-darwin parameter will be carried through, and all
# archive files will be dropped in the output directory with the same name as
# the binary and the .zip (for windows and darwin) or .tar.gz extension.
#
# If the --package-linux parameter is specified, all linux binaries will be
# packaged using ./package.sh. Requires the nfpm binary.
#
# If the --agpl parameter is specified, builds only the AGPL-licensed code (no
# Coder enterprise features).

set -euo pipefail
# shellcheck source=scripts/lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"

version=""
output_path=""
slim=0
sign_darwin=0
archive=0
package_linux=0
agpl=0

args="$(getopt -o "" -l version:,output:,slim,sign-darwin,archive,package-linux,agpl -- "$@")"
eval set -- "$args"
while true; do
	case "$1" in
	--version)
		version="$2"
		shift 2
		;;
	--output)
		output_path="$2"
		shift 2
		;;
	--slim)
		slim=1
		shift
		;;
	--sign-darwin)
		if [[ "${AC_APPLICATION_IDENTITY:-}" == "" ]]; then
			error "AC_APPLICATION_IDENTITY must be set when --sign-darwin is supplied"
		fi
		sign_darwin=1
		shift
		;;
	--archive)
		archive=1
		shift
		;;
	--package-linux)
		package_linux=1
		shift
		;;
	--agpl)
		agpl=1
		shift
		;;
	--)
		shift
		break
		;;
	*)
		error "Unrecognized option: $1"
		;;
	esac
done

# Verify the output path template.
if [[ "$output_path" == "" ]]; then
	# Input paths are relative, so we don't cdroot at the top, but for this case
	# we want it to be relative to the root.
	cdroot
	mkdir -p dist
	output_path="$(realpath "dist/coder_{version}_{os}_{arch}")"
elif [[ "$output_path" == */ ]]; then
	output_path="${output_path}coder_{version}_{os}_{arch}"
elif [[ "$output_path" != *"{os}"* ]] || [[ "$output_path" != *"{arch}"* ]]; then
	# If the output path isn't a directory (ends with /) then it must have
	# template variables.
	error "Templated output path '$output_path' must contain {os} and {arch}"
fi

mkdir -p "$(dirname "$output_path")"
output_path="$(realpath "$output_path")"

# Remove the "v" prefix.
version="${version#v}"
if [[ "$version" == "" ]]; then
	version="$(execrelative ./version.sh)"
fi

# Parse the os:arch specs into an array.
specs=()
may_zip=0
may_tar=0
for spec in "$@"; do
	spec_os="$(echo "$spec" | cut -d ":" -f 1)"
	if [[ "$spec_os" == "" ]] || [[ "$spec_os" == *" "* ]]; then
		error "Could not parse matrix build spec '$spec': invalid OS '$spec_os'"
	fi

	# Determine which dependencies we need.
	if [[ "$spec_os" == "windows" ]] || [[ "$spec_os" == "darwin" ]]; then
		may_zip=1
	else
		may_tar=1
	fi

	# No quoting is important here.
	for spec_arch in $(echo "$spec" | cut -d ":" -f 2 | tr "," "\n"); do
		if [[ "$spec_arch" == "" ]] || [[ "$spec_os" == *" "* ]]; then
			error "Could not parse matrix build spec '$spec': invalid architecture '$spec_arch'"
		fi

		specs+=("$spec_os:$spec_arch")
	done
done

# Remove duplicate specs while maintaining the same order.
specs_str="${specs[*]}"
specs=()
for s in $(echo "$specs_str" | tr " " "\n" | awk '!a[$0]++'); do
	specs+=("$s")
done

# Check dependencies
dependencies go
if [[ "$sign_darwin" == 1 ]]; then
	dependencies jq codesign gon
fi
if [[ "$archive" == 1 ]]; then
	if [[ "$may_zip" == 1 ]]; then
		dependencies zip
	fi
	if [[ "$may_tar" == 1 ]]; then
		dependencies tar
	fi
fi
if [[ "$package_linux" == 1 ]]; then
	dependencies nfpm
fi

bin_name="coder"
build_args=()
if [[ "$slim" == 1 ]]; then
	bin_name+="-slim"
	build_args+=(--slim)
fi
if [[ "$sign_darwin" == 1 ]]; then
	build_args+=(--sign-darwin)
fi
if [[ "$agpl" == 1 ]]; then
	build_args+=(--agpl)
fi

# Build each spec.
for spec in "${specs[@]}"; do
	spec_os="$(echo "$spec" | cut -d ":" -f 1)"
	spec_arch="$(echo "$spec" | cut -d ":" -f 2)"

	# Craft output path from the template.
	spec_output="$output_path"
	spec_output="${spec_output//\{os\}/"$spec_os"}"
	spec_output="${spec_output//\{arch\}/"$spec_arch"}"
	spec_output="${spec_output//\{version\}/"$version"}"

	spec_output_binary="$spec_output"
	if [[ "$spec_os" == "windows" ]]; then
		spec_output_binary+=".exe"
	fi

	# Ensure parent dir.
	mkdir -p "$(dirname "$spec_output")"

	log "--- Building $bin_name for $spec_os $spec_arch ($spec_output_binary)"
	execrelative ./build_go.sh \
		--version "$version" \
		--os "$spec_os" \
		--arch "$spec_arch" \
		--output "$spec_output_binary" \
		"${build_args[@]}"
	log
	log

	if [[ "$archive" == 1 ]]; then
		spec_archive_format="tar.gz"
		if [[ "$spec_os" == "windows" ]] || [[ "$spec_os" == "darwin" ]]; then
			spec_archive_format="zip"
		fi
		spec_output_archive="$spec_output.$spec_archive_format"

		archive_args=()
		if [[ "$sign_darwin" == 1 ]] && [[ "$spec_os" == "darwin" ]]; then
			archive_args+=(--sign-darwin)
		fi
		if [[ "$agpl" == 1 ]]; then
			archive_args+=(--agpl)
		fi

		log "--- Creating archive for $spec_os $spec_arch ($spec_output_archive)"
		execrelative ./archive.sh \
			--format "$spec_archive_format" \
			--output "$spec_output_archive" \
			"${archive_args[@]}" \
			"$spec_output_binary"
		log
		log
	fi

	if [[ "$package_linux" == 1 ]] && [[ "$spec_os" == "linux" ]]; then
		execrelative ./package.sh \
			--arch "$spec_arch" \
			--version "$version" \
			"$spec_output_binary"
		log
	fi
done
