guix-codesign raw

   1  #!/usr/bin/env bash
   2  export LC_ALL=C
   3  set -e -o pipefail
   4  
   5  # Source the common prelude, which:
   6  #   1. Checks if we're at the top directory of the Bitcoin Core repository
   7  #   2. Defines a few common functions and variables
   8  #
   9  # shellcheck source=libexec/prelude.bash
  10  source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
  11  
  12  
  13  ###################
  14  ## SANITY CHECKS ##
  15  ###################
  16  
  17  ################
  18  # Required non-builtin commands should be invocable
  19  ################
  20  
  21  check_tools cat mkdir git guix
  22  
  23  ################
  24  # Required env vars should be non-empty
  25  ################
  26  
  27  cmd_usage() {
  28      cat <<EOF
  29  Synopsis:
  30  
  31      env DETACHED_SIGS_REPO=<path/to/bitcoin-detached-sigs> \\
  32        ./contrib/guix/guix-codesign
  33  
  34  EOF
  35  }
  36  
  37  if [ -z "$DETACHED_SIGS_REPO" ]; then
  38      cmd_usage
  39      exit 1
  40  fi
  41  
  42  ################
  43  # GUIX_BUILD_OPTIONS should be empty
  44  ################
  45  #
  46  # GUIX_BUILD_OPTIONS is an environment variable recognized by guix commands that
  47  # can perform builds. This seems like what we want instead of
  48  # ADDITIONAL_GUIX_COMMON_FLAGS, but the value of GUIX_BUILD_OPTIONS is actually
  49  # _appended_ to normal command-line options. Meaning that they will take
  50  # precedence over the command-specific ADDITIONAL_GUIX_<CMD>_FLAGS.
  51  #
  52  # This seems like a poor user experience. Thus we check for GUIX_BUILD_OPTIONS's
  53  # existence here and direct users of this script to use our (more flexible)
  54  # custom environment variables.
  55  if [ -n "$GUIX_BUILD_OPTIONS" ]; then
  56  cat << EOF
  57  Error: Environment variable GUIX_BUILD_OPTIONS is not empty:
  58    '$GUIX_BUILD_OPTIONS'
  59  
  60  Unfortunately this script is incompatible with GUIX_BUILD_OPTIONS, please unset
  61  GUIX_BUILD_OPTIONS and use ADDITIONAL_GUIX_COMMON_FLAGS to set build options
  62  across guix commands or ADDITIONAL_GUIX_<CMD>_FLAGS to set build options for a
  63  specific guix command.
  64  
  65  See contrib/guix/README.md for more details.
  66  EOF
  67  exit 1
  68  fi
  69  
  70  ################
  71  # SOURCE_DATE_EPOCH should not unintentionally be set
  72  ################
  73  
  74  check_source_date_epoch
  75  
  76  ################
  77  # The codesignature git worktree should not be dirty
  78  ################
  79  
  80  if ! git -C "$DETACHED_SIGS_REPO" diff-index --quiet HEAD -- && [ -z "$FORCE_DIRTY_WORKTREE" ]; then
  81      cat << EOF
  82  ERR: The DETACHED CODESIGNATURE git worktree is dirty, which may lead to broken builds.
  83  
  84       Aborting...
  85  
  86  Hint: To make your git worktree clean, You may want to:
  87        1. Commit your changes,
  88        2. Stash your changes, or
  89        3. Set the 'FORCE_DIRTY_WORKTREE' environment variable if you insist on
  90           using a dirty worktree
  91  EOF
  92      exit 1
  93  fi
  94  
  95  ################
  96  # Build directories should not exist
  97  ################
  98  
  99  # Default to building for all supported HOSTs (overridable by environment)
 100  export HOSTS="${HOSTS:-x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin}"
 101  
 102  # Accumulate a list of build directories that already exist...
 103  hosts_distsrc_exists=""
 104  for host in $HOSTS; do
 105      if [ -e "$(distsrc_for_host "$host" codesigned)" ]; then
 106          hosts_distsrc_exists+=" ${host}"
 107      fi
 108  done
 109  
 110  if [ -n "$hosts_distsrc_exists" ]; then
 111  # ...so that we can print them out nicely in an error message
 112  cat << EOF
 113  ERR: Build directories for this commit already exist for the following platform
 114       triples you're attempting to build, probably because of previous builds.
 115       Please remove, or otherwise deal with them prior to starting another build.
 116  
 117       Aborting...
 118  
 119  Hint: To blow everything away, you may want to use:
 120  
 121    $ ./contrib/guix/guix-clean
 122  
 123  Specifically, this will remove all files without an entry in the index,
 124  excluding the SDK directory, the depends download cache, the depends built
 125  packages cache, the garbage collector roots for Guix environments, and the
 126  output directory.
 127  EOF
 128  for host in $hosts_distsrc_exists; do
 129      echo "     ${host} '$(distsrc_for_host "$host" codesigned)'"
 130  done
 131  exit 1
 132  else
 133      mkdir -p "$DISTSRC_BASE"
 134  fi
 135  
 136  
 137  ################
 138  # Codesigning tarballs SHOULD exist
 139  ################
 140  
 141  # Usage: codesigning_tarball_for_host HOST [BASE]
 142  #
 143  #   HOST: The current platform triple we're building for
 144  #   BASE: Optional. If provided, replaces ${OUTDIR_BASE}
 145  #
 146  codesigning_tarball_for_host() {
 147      case "$1" in
 148          *mingw*)
 149              echo "$(outdir_for_host "$1" "" "$2")/${DISTNAME}-win64-codesigning.tar.gz"
 150              ;;
 151          *darwin*)
 152              echo "$(outdir_for_host "$1" "" "$2")/${DISTNAME}-${1}-codesigning.tar.gz"
 153              ;;
 154          *)
 155              exit 1
 156              ;;
 157      esac
 158  }
 159  
 160  # Accumulate a list of build directories that already exist...
 161  hosts_codesigning_tarball_missing=""
 162  for host in $HOSTS; do
 163      if [ ! -e "$(codesigning_tarball_for_host "$host")" ]; then
 164          hosts_codesigning_tarball_missing+=" ${host}"
 165      fi
 166  done
 167  
 168  if [ -n "$hosts_codesigning_tarball_missing" ]; then
 169      # ...so that we can print them out nicely in an error message
 170      cat << EOF
 171  ERR: Codesigning tarballs do not exist
 172  ...
 173  
 174  EOF
 175  for host in $hosts_codesigning_tarball_missing; do
 176      echo "     ${host} '$(codesigning_tarball_for_host "$host")'"
 177  done
 178  exit 1
 179  fi
 180  
 181  ################
 182  # Check that we can connect to the guix-daemon
 183  ################
 184  
 185  cat << EOF
 186  Checking that we can connect to the guix-daemon...
 187  
 188  Hint: If this hangs, you may want to try turning your guix-daemon off and on
 189        again.
 190  
 191  EOF
 192  if ! guix gc --list-failures > /dev/null; then
 193      cat << EOF
 194  
 195  ERR: Failed to connect to the guix-daemon, please ensure that one is running and
 196       reachable.
 197  EOF
 198      exit 1
 199  fi
 200  
 201  # Developer note: we could use `guix repl` for this check and run:
 202  #
 203  #     (import (guix store)) (close-connection (open-connection))
 204  #
 205  # However, the internal API is likely to change more than the CLI invocation
 206  
 207  
 208  #########
 209  # SETUP #
 210  #########
 211  
 212  # Determine the maximum number of jobs to run simultaneously (overridable by
 213  # environment)
 214  JOBS="${JOBS:-$(nproc)}"
 215  
 216  # Determine the reference time used for determinism (overridable by environment)
 217  SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}"
 218  
 219  # Make sure an output directory exists for our builds
 220  OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}"
 221  mkdir -p "$OUTDIR_BASE"
 222  
 223  #########
 224  # BUILD #
 225  #########
 226  
 227  # Function to be called when codesigning for host ${1} and the user interrupts
 228  # the codesign
 229  int_trap() {
 230  cat << EOF
 231  ** INT received while codesigning ${1}, you may want to clean up the relevant
 232     work directories (e.g. distsrc-*) before recodesigning
 233  
 234  Hint: To blow everything away, you may want to use:
 235  
 236    $ ./contrib/guix/guix-clean
 237  
 238  Specifically, this will remove all files without an entry in the index,
 239  excluding the SDK directory, the depends download cache, the depends built
 240  packages cache, the garbage collector roots for Guix environments, and the
 241  output directory.
 242  EOF
 243  }
 244  
 245  # Deterministically build Bitcoin Core
 246  # shellcheck disable=SC2153
 247  for host in $HOSTS; do
 248  
 249      # Display proper warning when the user interrupts the build
 250      trap 'int_trap ${host}' INT
 251  
 252      (
 253          # Required for 'contrib/guix/manifest.scm' to output the right manifest
 254          # for the particular $HOST we're building for
 255          export HOST="$host"
 256  
 257  cat << EOF
 258  INFO: Codesigning ${VERSION:?not set} for platform triple ${HOST:?not set}:
 259        ...using reference timestamp: ${SOURCE_DATE_EPOCH:?not set}
 260        ...from worktree directory: '${PWD}'
 261            ...bind-mounted in container to: '/bitcoin'
 262        ...in build directory: '$(distsrc_for_host "$HOST" codesigned)'
 263            ...bind-mounted in container to: '$(distsrc_for_host "$HOST" codesigned /distsrc-base)'
 264        ...outputting in: '$(outdir_for_host "$HOST" codesigned)'
 265            ...bind-mounted in container to: '$(outdir_for_host "$HOST" codesigned /outdir-base)'
 266        ...using detached signatures in: '${DETACHED_SIGS_REPO:?not set}'
 267            ...bind-mounted in container to: '/detached-sigs'
 268  EOF
 269  
 270  
 271          # Run the build script 'contrib/guix/libexec/codesign.sh' in the build
 272          # container specified by 'contrib/guix/manifest.scm'.
 273          #
 274          # Explanation of `guix shell` flags:
 275          #
 276          #   --container        run command within an isolated container
 277          #
 278          #     Running in an isolated container minimizes build-time differences
 279          #     between machines and improves reproducibility
 280          #
 281          #   --writable-root    make the root filesystem writable
 282          #
 283          #   --pure             unset existing environment variables
 284          #
 285          #     Same rationale as --container
 286          #
 287          #   --no-cwd           do not share current working directory with an
 288          #                      isolated container
 289          #
 290          #     When --container is specified, the default behavior is to share
 291          #     the current working directory with the isolated container at the
 292          #     same exact path (e.g. mapping '/home/satoshi/bitcoin/' to
 293          #     '/home/satoshi/bitcoin/'). This means that the $PWD inside the
 294          #     container becomes a source of irreproducibility. --no-cwd disables
 295          #     this behaviour.
 296          #
 297          #   --share=SPEC       for containers, share writable host file system
 298          #                      according to SPEC
 299          #
 300          #   --share="$PWD"=/bitcoin
 301          #
 302          #                     maps our current working directory to /bitcoin
 303          #                     inside the isolated container, which we later cd
 304          #                     into.
 305          #
 306          #     While we don't want to map our current working directory to the
 307          #     same exact path (as this introduces irreproducibility), we do want
 308          #     it to be at a _fixed_ path _somewhere_ inside the isolated
 309          #     container so that we have something to build. '/bitcoin' was
 310          #     chosen arbitrarily.
 311          #
 312          #  ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"}
 313          #
 314          #                     fetch substitute from SUBSTITUTE_URLS if they are
 315          #                     authorized
 316          #
 317          #    Depending on the user's security model, it may be desirable to use
 318          #    substitutes (pre-built packages) from servers that the user trusts.
 319          #    Please read the README.md in the same directory as this file for
 320          #    more information.
 321          #
 322          # shellcheck disable=SC2086
 323          time-machine shell --manifest="${PWD}/contrib/guix/manifest_codesign.scm" \
 324                                   --container \
 325                                   --writable-root \
 326                                   --pure \
 327                                   --no-cwd \
 328                                   --share="$PWD"=/bitcoin \
 329                                   --share="$DISTSRC_BASE"=/distsrc-base \
 330                                   --share="$OUTDIR_BASE"=/outdir-base \
 331                                   --share="$DETACHED_SIGS_REPO"=/detached-sigs \
 332                                   --expose="$(git rev-parse --git-common-dir)" \
 333                                   --expose="$(git -C "$DETACHED_SIGS_REPO" rev-parse --git-common-dir)" \
 334                                   --cores="$JOBS" \
 335                                   --keep-failed \
 336                                   --fallback \
 337                                   --link-profile \
 338                                   --root="$(profiledir_for_host "${HOST}" codesigned)" \
 339                                   ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
 340                                   ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \
 341                                   -- env HOST="$host" \
 342                                          DISTNAME="$DISTNAME" \
 343                                          JOBS="$JOBS" \
 344                                          SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \
 345                                          ${V:+V=1} \
 346                                          DISTSRC="$(distsrc_for_host "$HOST" codesigned /distsrc-base)" \
 347                                          OUTDIR="$(outdir_for_host "$HOST" codesigned /outdir-base)" \
 348                                          DIST_ARCHIVE_BASE=/outdir-base/dist-archive \
 349                                          DETACHED_SIGS_REPO=/detached-sigs \
 350                                          CODESIGNING_TARBALL="$(codesigning_tarball_for_host "$HOST" /outdir-base)" \
 351                                        bash -c "cd /bitcoin && bash contrib/guix/libexec/codesign.sh"
 352      )
 353  
 354  done
 355