#!/bin/bash # Copyright 2019 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 -xeou pipefail # Remember our current directory. declare orig_dir orig_dir=$(pwd) readonly orig_dir # Record the current working commit. declare head head=$(git describe --always) readonly head # Create a temporary working directory, and ensure that this directory and all # subdirectories are cleaned up upon exit. declare tmp_dir tmp_dir=$(mktemp -d) readonly tmp_dir finish() { cd "${orig_dir}" # Leave tmp_dir. rm -rf "${tmp_dir}" # Remove all contents. git checkout -f "${head}" # Restore commit. } trap finish EXIT # Discover the package name from the go.mod file. declare module origpwd othersrc module=$(cat go.mod | grep -E "^module" | cut -d' ' -f2) origpwd=$(pwd) othersrc=("go.mod" "go.sum" "AUTHORS" "LICENSE") readonly module origpwd othersrc # Build an amd64 & arm64 gopath. declare -r go_amd64="${tmp_dir}/amd64" declare -r go_arm64="${tmp_dir}/arm64" make build BAZEL_OPTIONS="" TARGETS="//:gopath" rsync --recursive --delete --copy-links bazel-bin/gopath/ "${go_amd64}" make build BAZEL_OPTIONS=--config=cross-aarch64 TARGETS="//:gopath" 2>/dev/null rsync --recursive --delete --copy-links bazel-bin/gopath/ "${go_arm64}" # Strip irrelevant files, i.e. use only arm64 files from the arm64 build. # This is because bazel may generate incorrect files for non-target platforms # as a workaround. See pkg/sentry/loader/vdsodata as an example. find "${go_amd64}/src/${module}" -name '*_arm64*.go' -exec rm -f {} \; find "${go_amd64}/src/${module}" -name '*_arm64*.s' -exec rm -f {} \; find "${go_arm64}/src/${module}" -name '*_amd64*.go' -exec rm -f {} \; find "${go_arm64}/src/${module}" -name '*_amd64*.s' -exec rm -f {} \; # See below. The certs.go file is pseudo-random, and therefore will also # differ between the branches. Since we merge, it only has to come from one. # We arbitrarily keep the one from the amd64 branch, and drop the arm64 one. rm -f "${go_arm64}/src/${module}/webhook/pkg/injector/certs.go" # Check that all files are compatible. This means that if the files exist in # both architectures, then they must be identical. The only ones that we expect # to exist in a single architecture (due to binary builds) may be different. function cross_check() { (cd "${1}" && find "src/${module}" -type f | \ xargs -n 1 -I {} sh -c "diff '${1}/{}' '${2}/{}' 2>/dev/null; test \$? -ne 1") } cross_check "${go_arm64}" "${go_amd64}" cross_check "${go_amd64}" "${go_arm64}" # Merge the two for a complete set of source files. declare -r go_merged="${tmp_dir}/merged" rsync --recursive "${go_amd64}/" "${go_merged}" rsync --recursive "${go_arm64}/" "${go_merged}" # We expect to have an existing go branch that we will use as the basis for this # commit. That branch may be empty, but it must exist. We search for this branch # using the local branch, the "origin" branch, and other remotes, in order. git fetch --all declare go_branch go_branch=$( \ git show-ref --hash refs/heads/go || \ git show-ref --hash refs/remotes/origin/go || \ git show-ref --hash go | head -n 1 \ ) readonly go_branch # Clone the current repository to the temporary directory, and check out the # current go_branch directory. We move to the new repository for convenience. declare repo_orig repo_orig="$(pwd)" readonly repo_orig declare -r repo_new="${tmp_dir}/repository" git clone . "${repo_new}" cd "${repo_new}" # Setup the repository and checkout the branch. git config user.email "gvisor-bot@google.com" git config user.name "gVisor bot" git fetch origin "${go_branch}" git checkout -b go "${go_branch}" # Start working on a merge commit that combines the previous history with the # current history. Note that we don't actually want any changes yet. # # N.B. The git behavior changed at some point and the relevant flag was added # to allow for override, so try the only behavior first then pass the flag. git merge --no-commit --strategy ours "${head}" || \ git merge --allow-unrelated-histories --no-commit --strategy ours "${head}" # Normalize the permissions on the old branch. Note that they should be # normalized if constructed by this tool, but we do so before the rsync. find . -type f -exec chmod 0644 {} \; find . -type d -exec chmod 0755 {} \; # Sync the entire gopath. Note that we exclude auto-generated source files that # will change here. Otherwise, it adds a tremendous amount of noise to commits. # If this file disappears in the future, then presumably we will still delete # the underlying directory. declare -r gopath="${go_merged}/src/${module}" rsync --recursive --delete \ --exclude .git \ --exclude webhook/pkg/injector/certs.go \ "${gopath}/" . # Add additional files. for file in "${othersrc[@]}"; do cp "${origpwd}"/"${file}" . done # Construct a new README.md. cat > README.md <<EOF # gVisor This branch is a synthetic branch, containing only Go sources, that is compatible with standard Go tools. See the master branch for authoritative sources and tests. EOF # There are a few solitary files that can get left behind due to the way bazel # constructs the gopath target. Note that we don't find all Go files here # because they may correspond to unused templates, etc. declare -ar binaries=( "runsc" "shim" "webhook" ) for target in "${binaries[@]}"; do mkdir -p "${target}" cp "${repo_orig}/${target}"/*.go "${target}/" done # Normalize all permissions. The way bazel constructs the :gopath tree may leave # some strange permissions on files. We don't have anything in this tree that # should be execution, only the Go source files, README.md, and ${othersrc}. find . -type f -exec chmod 0644 {} \; find . -type d -exec chmod 0755 {} \; # Update the current working set and commit. # If the current working commit has already been committed to the remote go # branch, then we have nothing to commit here. So allow empty commit. This can # occur when this script is run parallely (via pull_request and push events) # and the push workflow finishes before the pull_request workflow can run this. git add --all && git commit --allow-empty -m "Merge ${head} (automated)" # Push the branch back to the original repository. git remote add orig "${repo_orig}" && git push -f orig go:go