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