#!/bin/bash

# Copyright 2025 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -x -e -o pipefail

dst=$(realpath "$1")
gopath_zip="$2"
go_mod="$3"
go_sum="$4"
runsc_main_go="$5"
golang_patch=$(realpath "$6")
race="$7"

mkdir .gocache
GOMODCACHE="$(pwd)/.gocache"
GOCACHE="$(pwd)/.gocache"
export GOMODCACHE GOCACHE

(
  # The gVisor code coverage implementation uses internal packages. More details
  # can be found here: https://github.com/golang/go/issues/76098.

  go_version=1.26.3

  go_base_url="https://dl.google.com/go/go${go_version}"
  arch="$(uname -m)"
  case "$arch" in
      x86_64) go_arch="amd64" ;;
      aarch64) go_arch="arm64" ;;
      *) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
  esac

  mkdir go-bootstrap
  curl -fsSL "${go_base_url}.linux-${go_arch}.tar.gz" | tar -xz -C go-bootstrap
  GOROOT_BOOTSTRAP="$(pwd)/go-bootstrap/go"
  export GOROOT_BOOTSTRAP

  curl -fsSL "${go_base_url}.src.tar.gz" | tar -xz
  cd go
  patch -p1 < "$golang_patch"
  cd src
  ./make.bash
)

goroot_dir="$(pwd)/go"
go_tool="$goroot_dir/bin/go"

gvisor_gopath="gopath"

unzip -q "$gopath_zip" -d "$gvisor_gopath"
cp "$go_mod" "$go_sum" "$gvisor_gopath/src/gvisor.dev/gvisor/"
mkdir -p "$gvisor_gopath/src/gvisor.dev/gvisor/runsc"
cp "$runsc_main_go" "$gvisor_gopath/src/gvisor.dev/gvisor/runsc/main.go"
cd "$gvisor_gopath/src/gvisor.dev/gvisor/"
export GOROOT="$goroot_dir"
go_opts=""
go_tags="kcov,opensource"
if [[ "$race" = true ]]; then
  go_opts="-race"
  go_tags="$go_tags,lockdep"
else
  # runsc has to be a self-contained binary to be able to run it in a minimal
  # mount namespace.
  export CGO_ENABLED=0
fi
# sleep, sync, syncevent use go:norace, which is not respected by coverage
# instrumentation. Race builds will be instrumented with atomic coverage (using
# sync/atomic.AddInt32), which will not work. We may be able to re-enable
# coverage on them when https://golang.org/issue/43007 is resolved.
# bpf is excluded because `bpf.Optimize` runs at sandbox startup and is a
# very expensive loop that restarts on every change.
# Under -covermode=atomic each of those blocks does a sync/atomic counter
# increment, and in a race build every one of those atomics additionally goes
# through the race detector on top, which compounds the issue and can make it
# take 10s of seconds for a codepath that is in practive never exercised on
# real ssndboxes, because seccomp filters are precompiled there. The only
# reason this runs at startup at all is because the presence of
# instrumentation itself changes the set of required seccomp filters, and we
# don't want to precompile the BPF instruction for those into the real runsc
# binary, so it is forced to run from scratch.
gopkgs=$("$go_tool" list ./... | grep -E -v 'pkg/sentry/platform|pkg/ring0|pkg/coverage|pkg/sleep|pkg/sync|pkg/syncevent|pkg/bpf' | paste -sd,)
"$go_tool" build --tags "$go_tags" $go_opts -cover -coverpkg="$gopkgs" -covermode=atomic -o "$dst" runsc/main.go
