coder/scripts/build_go.sh
Dean Sheather 64fec8bf0b
Some checks are pending
ci / changes (push) Waiting to run
ci / lint (push) Blocked by required conditions
ci / gen (push) Waiting to run
ci / fmt (push) Blocked by required conditions
ci / test-go (macos-latest) (push) Blocked by required conditions
ci / test-go (ubuntu-latest) (push) Blocked by required conditions
ci / test-go (windows-2022) (push) Blocked by required conditions
ci / test-cli (macos-latest) (push) Blocked by required conditions
ci / test-cli (windows-2022) (push) Blocked by required conditions
ci / test-go-pg (ubuntu-latest) (push) Blocked by required conditions
ci / test-go-pg-16 (push) Blocked by required conditions
ci / test-go-race (push) Blocked by required conditions
ci / test-go-race-pg (push) Blocked by required conditions
ci / test-go-tailnet-integration (push) Blocked by required conditions
ci / test-js (push) Blocked by required conditions
ci / test-e2e (push) Blocked by required conditions
ci / test-e2e-premium (push) Blocked by required conditions
ci / chromatic (push) Blocked by required conditions
ci / offlinedocs (push) Blocked by required conditions
ci / required (push) Blocked by required conditions
ci / build-dylib (push) Blocked by required conditions
ci / build (push) Blocked by required conditions
ci / deploy (push) Blocked by required conditions
ci / deploy-wsproxies (push) Blocked by required conditions
ci / sqlc-vet (push) Blocked by required conditions
ci / notify-slack-on-failure (push) Blocked by required conditions
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
feat: include winres metadata in Windows binaries (#16706)
Adds information like product/file version, description, product name
and copyright to compiled Windows binaries in dogfood and release
builds. Also adds an icon to the executable.

This is necessary for Coder Desktop to be able to check the version on
binaries.

### Before:

![image](https://github.com/user-attachments/assets/82351b63-6b23-4ef8-ab89-7f9e6dafeabd)

![image](https://github.com/user-attachments/assets/d17d8098-e330-4ac0-b104-31163f84279f)

### After:

![image](https://github.com/user-attachments/assets/0ba50afa-ad53-4ad2-b5e2-557358cda037)

![image](https://github.com/user-attachments/assets/d305cc27-e3f3-41a8-9098-498b71344faa)

![image](https://github.com/user-attachments/assets/42f74ace-bda1-414f-b514-68d4d928c958)

Closes https://github.com/coder/coder/issues/16693
2025-02-28 16:03:08 +11:00

323 lines
8.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# This script builds a single Go binary of Coder with the given parameters.
#
# Usage: ./build_go.sh [--version 1.2.3-devel+abcdef] [--os linux] [--arch amd64] [--output path/to/output] [--slim] [--agpl] [--boringcrypto] [--dylib]
#
# Defaults to linux:amd64 with slim disabled, but can be controlled with GOOS,
# GOARCH and CODER_SLIM_BUILD=1. If no version is specified, defaults to the
# version from ./version.sh.
#
# GOARM can be controlled by suffixing any arm architecture (i.e. arm or arm64)
# with "vX" (e.g. "v7", "v8").
#
# Unless overridden via --output, the built binary will be dropped in
# "$repo_root/build/coder_$version_$os_$arch" (with a ".exe" suffix for windows
# builds) and the absolute path to the binary will be printed to stdout on
# completion.
#
# If the --sign-darwin parameter is specified and the OS is darwin, the output
# binary will be signed using ./sign_darwin.sh. Read that file for more details
# on the requirements.
#
# If the --agpl parameter is specified, builds only the AGPL-licensed code (no
# Coder enterprise features).
#
# If the --boringcrypto parameter is specified, builds use boringcrypto instead of
# the standard go crypto libraries.
#
# If the --dylib parameter is specified, the Coder Desktop `.dylib` is built
# instead of the standard binary. This is only supported on macOS arm64 & amd64.
set -euo pipefail
# shellcheck source=scripts/lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
version=""
os="${GOOS:-linux}"
arch="${GOARCH:-amd64}"
output_path=""
slim="${CODER_SLIM_BUILD:-0}"
agpl="${CODER_BUILD_AGPL:-0}"
sign_darwin="${CODER_SIGN_DARWIN:-0}"
sign_windows="${CODER_SIGN_WINDOWS:-0}"
boringcrypto=${CODER_BUILD_BORINGCRYPTO:-0}
dylib=0
windows_resources="${CODER_WINDOWS_RESOURCES:-0}"
debug=0
bin_ident="com.coder.cli"
args="$(getopt -o "" -l version:,os:,arch:,output:,slim,agpl,sign-darwin,sign-windows,boringcrypto,dylib,windows-resources,debug -- "$@")"
eval set -- "$args"
while true; do
case "$1" in
--version)
version="$2"
shift 2
;;
--os)
os="$2"
shift 2
;;
--arch)
arch="$2"
shift 2
;;
--output)
mkdir -p "$(dirname "$2")"
output_path="$(realpath "$2")"
shift 2
;;
--slim)
slim=1
shift
;;
--agpl)
agpl=1
shift
;;
--sign-darwin)
sign_darwin=1
shift
;;
--sign-windows)
sign_windows=1
shift
;;
--boringcrypto)
boringcrypto=1
shift
;;
--dylib)
dylib=1
shift
;;
--windows-resources)
windows_resources=1
shift
;;
--debug)
debug=1
shift
;;
--)
shift
break
;;
*)
error "Unrecognized option: $1"
;;
esac
done
cdroot
# Remove the "v" prefix.
version="${version#v}"
if [[ "$version" == "" ]]; then
version="$(execrelative ./version.sh)"
fi
# Check dependencies
dependencies go
if [[ "$sign_darwin" == 1 ]]; then
dependencies rcodesign
requiredenvs AC_CERTIFICATE_FILE AC_CERTIFICATE_PASSWORD_FILE
fi
if [[ "$sign_windows" == 1 ]]; then
dependencies java
requiredenvs JSIGN_PATH EV_KEYSTORE EV_KEY EV_CERTIFICATE_PATH EV_TSA_URL GCLOUD_ACCESS_TOKEN
fi
if [[ "$windows_resources" == 1 ]]; then
dependencies go-winres
fi
ldflags=(
-X "'github.com/coder/coder/v2/buildinfo.tag=$version'"
)
# Disable deubgger information if not building a binary for debuggers.
if [[ "$debug" == 0 ]]; then
ldflags+=(-s -w)
fi
# We use ts_omit_aws here because on Linux it prevents Tailscale from importing
# github.com/aws/aws-sdk-go-v2/aws, which adds 7 MB to the binary.
TS_EXTRA_SMALL="ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube"
if [[ "$slim" == 0 ]]; then
build_args+=(-tags "embed,$TS_EXTRA_SMALL")
else
build_args+=(-tags "slim,$TS_EXTRA_SMALL")
fi
if [[ "$agpl" == 1 ]]; then
# We don't use a tag to control AGPL because we don't want code to depend on
# a flag to control AGPL vs. enterprise behavior.
ldflags+=(-X "'github.com/coder/coder/v2/buildinfo.agpl=true'")
fi
cgo=0
if [[ "$dylib" == 1 ]]; then
if [[ "$os" != "darwin" ]]; then
error "dylib builds are not supported on $os"
fi
cgo=1
build_args+=("-buildmode=c-shared")
SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
export SDKROOT
bin_ident="com.coder.Coder-Desktop.VPN.dylib"
plist_file=$(mktemp)
trap 'rm -f "$plist_file"' EXIT
# CFBundleShortVersionString must be in the format /[0-9]+.[0-9]+.[0-9]+/
# CFBundleVersion can be in any format
BUNDLE_IDENTIFIER="$bin_ident" VERSION_STRING="$version" SHORT_VERSION_STRING=$(echo "$version" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+') \
execrelative envsubst <"$(realpath ./vpn/dylib/info.plist.tmpl)" >"$plist_file"
ldflags+=("-extldflags '-sectcreate __TEXT __info_plist $plist_file'")
fi
build_args+=(-ldflags "${ldflags[*]}")
# Disable optimizations if building a binary for debuggers.
if [[ "$debug" == 1 ]]; then
build_args+=(-gcflags "all=-N -l")
fi
# Compute default output path.
if [[ "$output_path" == "" ]]; then
mkdir -p "build"
output_path="build/coder_${version}_${os}_${arch}"
if [[ "$os" == "windows" ]]; then
output_path+=".exe"
fi
output_path="$(realpath "$output_path")"
fi
build_args+=(-o "$output_path")
# Determine GOARM.
arm_version=""
if [[ "$arch" == "arm" ]]; then
arm_version="7"
elif [[ "$arch" == "armv"* ]] || [[ "$arch" == "arm64v"* ]]; then
arm_version="${arch//*v/}"
# Remove the v* suffix.
arch="${arch//v*/}"
fi
cmd_path="./enterprise/cmd/coder"
if [[ "$agpl" == 1 ]]; then
cmd_path="./cmd/coder"
fi
if [[ "$dylib" == 1 ]]; then
cmd_path="./vpn/dylib/lib.go"
fi
goexp=""
if [[ "$boringcrypto" == 1 ]]; then
cgo=1
goexp="boringcrypto"
fi
# On Windows, we use go-winres to embed the resources into the binary.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
# Convert the version to a format that Windows understands.
# Remove any trailing data after a "+" or "-".
version_windows=$version
version_windows="${version_windows%+*}"
version_windows="${version_windows%-*}"
# If there wasn't any extra data, add a .0 to the version. Otherwise, add
# a .1 to the version to signify that this is not a release build so it can
# be distinguished from a release build.
non_release_build=0
if [[ "$version_windows" == "$version" ]]; then
version_windows+=".0"
else
version_windows+=".1"
non_release_build=1
fi
if [[ ! "$version_windows" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-1]$ ]]; then
error "Computed invalid windows version format: $version_windows"
fi
# File description changes based on slimness, AGPL status, and architecture.
file_description="Coder"
if [[ "$agpl" == 1 ]]; then
file_description+=" AGPL"
fi
if [[ "$slim" == 1 ]]; then
file_description+=" CLI"
fi
if [[ "$non_release_build" == 1 ]]; then
file_description+=" (development build)"
fi
# Because this writes to a file with the OS and arch in the filename, we
# don't support concurrent builds for the same OS and arch (irregardless of
# slimness or AGPL status).
#
# This is fine since we only embed resources during dogfood and release
# builds, which use make (which will build all slim targets in parallel,
# then all non-slim targets in parallel).
expected_rsrc_file="./buildinfo/resources/resources_windows_${arch}.syso"
if [[ -f "$expected_rsrc_file" ]]; then
rm "$expected_rsrc_file"
fi
touch "$expected_rsrc_file"
pushd ./buildinfo/resources
GOARCH="$arch" go-winres simply \
--arch "$arch" \
--out "resources" \
--product-version "$version_windows" \
--file-version "$version_windows" \
--manifest "cli" \
--file-description "$file_description" \
--product-name "Coder" \
--copyright "Copyright $(date +%Y) Coder Technologies Inc." \
--original-filename "coder.exe" \
--icon ../../scripts/win-installer/coder.ico
popd
if [[ ! -f "$expected_rsrc_file" ]]; then
error "Failed to generate $expected_rsrc_file"
fi
fi
set +e
GOEXPERIMENT="$goexp" CGO_ENABLED="$cgo" GOOS="$os" GOARCH="$arch" GOARM="$arm_version" \
go build \
"${build_args[@]}" \
"$cmd_path" 1>&2
exit_code=$?
set -e
# Clean up the resources file if it was generated.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
rm "$expected_rsrc_file"
fi
if [[ "$exit_code" != 0 ]]; then
exit "$exit_code"
fi
# If we did embed resources, verify that they were included.
if [[ "$windows_resources" == 1 ]] && [[ "$os" == "windows" ]]; then
winres_dir=$(mktemp -d)
if ! go-winres extract --dir "$winres_dir" "$output_path" 1>&2; then
rm -rf "$winres_dir"
error "Compiled binary does not contain embedded resources"
fi
# If go-winres didn't return an error, it means it did find embedded
# resources.
rm -rf "$winres_dir"
fi
if [[ "$sign_darwin" == 1 ]] && [[ "$os" == "darwin" ]]; then
execrelative ./sign_darwin.sh "$output_path" "$bin_ident" 1>&2
fi
if [[ "$sign_windows" == 1 ]] && [[ "$os" == "windows" ]]; then
execrelative ./sign_windows.sh "$output_path" 1>&2
fi
echo "$output_path"