build.sh raw
1 #!/usr/bin/env bash
2 # Copyright (c) 2019-2022 The Limenka developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 export LC_ALL=C
6 set -e -o pipefail
7 export TZ=UTC
8
9 # Although Guix _does_ set umask when building its own packages (in our case,
10 # this is all packages in manifest.scm), it does not set it for `guix
11 # shell`. It does make sense for at least `guix shell --container`
12 # to set umask, so if that change gets merged upstream and we bump the
13 # time-machine to a commit which includes the aforementioned change, we can
14 # remove this line.
15 #
16 # This line should be placed before any commands which creates files.
17 umask 0022
18
19 if [ -n "$V" ]; then
20 # Print both unexpanded (-v) and expanded (-x) forms of commands as they are
21 # read from this file.
22 set -vx
23 # Set VERBOSE for CMake-based builds
24 export VERBOSE="$V"
25 fi
26
27 # Check that required environment variables are set
28 cat << EOF
29 Required environment variables as seen inside the container:
30 DIST_ARCHIVE_BASE: ${DIST_ARCHIVE_BASE:?not set}
31 DISTNAME: ${DISTNAME:?not set}
32 HOST: ${HOST:?not set}
33 SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:?not set}
34 JOBS: ${JOBS:?not set}
35 DISTSRC: ${DISTSRC:?not set}
36 OUTDIR: ${OUTDIR:?not set}
37 EOF
38
39 ACTUAL_OUTDIR="${OUTDIR}"
40 OUTDIR="${DISTSRC}/output"
41
42 #####################
43 # Environment Setup #
44 #####################
45
46 # The depends folder also serves as a base-prefix for depends packages for
47 # $HOSTs after successfully building.
48 BASEPREFIX="${PWD}/depends"
49
50 # Given a package name and an output name, return the path of that output in our
51 # current guix environment
52 store_path() {
53 grep --extended-regexp "/[^-]{32}-${1}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \
54 | head --lines=1 \
55 | sed --expression='s|\x29*$||' \
56 --expression='s|^[[:space:]]*"||' \
57 --expression='s|"[[:space:]]*$||'
58 }
59
60
61 # Set environment variables to point the NATIVE toolchain to the right
62 # includes/libs
63 NATIVE_GCC="$(store_path gcc-toolchain)"
64
65 unset LIBRARY_PATH
66 unset CPATH
67 unset C_INCLUDE_PATH
68 unset CPLUS_INCLUDE_PATH
69 unset OBJC_INCLUDE_PATH
70 unset OBJCPLUS_INCLUDE_PATH
71
72 export C_INCLUDE_PATH="${NATIVE_GCC}/include"
73 export CPLUS_INCLUDE_PATH="${NATIVE_GCC}/include/c++:${NATIVE_GCC}/include"
74
75 case "$HOST" in
76 *darwin*) export LIBRARY_PATH="${NATIVE_GCC}/lib" ;; # Required for qt/qmake
77 *mingw*) export LIBRARY_PATH="${NATIVE_GCC}/lib" ;;
78 *)
79 NATIVE_GCC_STATIC="$(store_path gcc-toolchain static)"
80 export LIBRARY_PATH="${NATIVE_GCC}/lib:${NATIVE_GCC_STATIC}/lib"
81 ;;
82 esac
83
84 # Set environment variables to point the CROSS toolchain to the right
85 # includes/libs for $HOST
86 case "$HOST" in
87 *mingw*)
88 # Determine output paths to use in CROSS_* environment variables
89 CROSS_GLIBC="$(store_path "mingw-w64-x86_64-winpthreads")"
90 CROSS_GCC="$(store_path "gcc-cross-${HOST}")"
91 CROSS_GCC_LIB_STORE="$(store_path "gcc-cross-${HOST}" lib)"
92 CROSS_GCC_LIBS=( "${CROSS_GCC_LIB_STORE}/lib/gcc/${HOST}"/* ) # This expands to an array of directories...
93 CROSS_GCC_LIB="${CROSS_GCC_LIBS[0]}" # ...we just want the first one (there should only be one)
94
95 # The search path ordering is generally:
96 # 1. gcc-related search paths
97 # 2. libc-related search paths
98 # 2. kernel-header-related search paths (not applicable to mingw-w64 hosts)
99 export CROSS_C_INCLUDE_PATH="${CROSS_GCC_LIB}/include:${CROSS_GCC_LIB}/include-fixed:${CROSS_GLIBC}/include"
100 export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GCC}/include/c++/${HOST}:${CROSS_GCC}/include/c++/backward:${CROSS_C_INCLUDE_PATH}"
101 export CROSS_LIBRARY_PATH="${CROSS_GCC_LIB_STORE}/lib:${CROSS_GCC_LIB}:${CROSS_GLIBC}/lib"
102 ;;
103 *darwin*)
104 # The CROSS toolchain for darwin uses the SDK and ignores environment variables.
105 # See depends/hosts/darwin.mk for more details.
106 ;;
107 *linux*)
108 CROSS_GLIBC="$(store_path "glibc-cross-${HOST}")"
109 CROSS_GLIBC_STATIC="$(store_path "glibc-cross-${HOST}" static)"
110 CROSS_KERNEL="$(store_path "linux-libre-headers-cross-${HOST}")"
111 CROSS_GCC="$(store_path "gcc-cross-${HOST}")"
112 CROSS_GCC_LIB_STORE="$(store_path "gcc-cross-${HOST}" lib)"
113 CROSS_GCC_LIBS=( "${CROSS_GCC_LIB_STORE}/lib/gcc/${HOST}"/* ) # This expands to an array of directories...
114 CROSS_GCC_LIB="${CROSS_GCC_LIBS[0]}" # ...we just want the first one (there should only be one)
115
116 export CROSS_C_INCLUDE_PATH="${CROSS_GCC_LIB}/include:${CROSS_GCC_LIB}/include-fixed:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include"
117 export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GCC}/include/c++/${HOST}:${CROSS_GCC}/include/c++/backward:${CROSS_C_INCLUDE_PATH}"
118 export CROSS_LIBRARY_PATH="${CROSS_GCC_LIB_STORE}/lib:${CROSS_GCC_LIB}:${CROSS_GLIBC}/lib:${CROSS_GLIBC_STATIC}/lib"
119 ;;
120 *)
121 exit 1 ;;
122 esac
123
124 # Sanity check CROSS_*_PATH directories
125 IFS=':' read -ra PATHS <<< "${CROSS_C_INCLUDE_PATH}:${CROSS_CPLUS_INCLUDE_PATH}:${CROSS_LIBRARY_PATH}"
126 for p in "${PATHS[@]}"; do
127 if [ -n "$p" ] && [ ! -d "$p" ]; then
128 echo "'$p' doesn't exist or isn't a directory... Aborting..."
129 exit 1
130 fi
131 done
132
133 # Disable Guix ld auto-rpath behavior
134 export GUIX_LD_WRAPPER_DISABLE_RPATH=yes
135
136 # Make /usr/bin if it doesn't exist
137 [ -e /usr/bin ] || mkdir -p /usr/bin
138
139 # Symlink file and env to a conventional path
140 [ -e /usr/bin/file ] || ln -s --no-dereference "$(command -v file)" /usr/bin/file
141 [ -e /usr/bin/env ] || ln -s --no-dereference "$(command -v env)" /usr/bin/env
142
143 # Determine the correct value for -Wl,--dynamic-linker for the current $HOST
144 case "$HOST" in
145 *linux*)
146 glibc_dynamic_linker=$(
147 case "$HOST" in
148 x86_64-linux-gnu) echo /lib64/ld-linux-x86-64.so.2 ;;
149 arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;;
150 aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;;
151 riscv64-linux-gnu) echo /lib/ld-linux-riscv64-lp64d.so.1 ;;
152 powerpc64-linux-gnu) echo /lib64/ld64.so.1;;
153 powerpc64le-linux-gnu) echo /lib64/ld64.so.2;;
154 *) exit 1 ;;
155 esac
156 )
157 ;;
158 esac
159
160 # Environment variables for determinism
161 export TAR_OPTIONS="--owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name"
162 export TZ="UTC"
163
164 ####################
165 # Depends Building #
166 ####################
167
168 # Build the depends tree, overriding variables that assume multilib gcc
169 make -C depends --jobs="$JOBS" HOST="$HOST" \
170 ${V:+V=1} \
171 ${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \
172 ${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \
173 ${SDK_PATH+SDK_PATH="$SDK_PATH"} \
174 x86_64_linux_CC=x86_64-linux-gnu-gcc \
175 x86_64_linux_CXX=x86_64-linux-gnu-g++ \
176 x86_64_linux_AR=x86_64-linux-gnu-gcc-ar \
177 x86_64_linux_RANLIB=x86_64-linux-gnu-gcc-ranlib \
178 x86_64_linux_NM=x86_64-linux-gnu-gcc-nm \
179 x86_64_linux_STRIP=x86_64-linux-gnu-strip
180
181 case "$HOST" in
182 *darwin*)
183 # Unset now that Qt is built
184 unset C_INCLUDE_PATH
185 unset CPLUS_INCLUDE_PATH
186 unset LIBRARY_PATH
187 ;;
188 esac
189
190 ###########################
191 # Source Tarball Building #
192 ###########################
193
194 GIT_ARCHIVE="${DIST_ARCHIVE_BASE}/${DISTNAME}.tar.gz"
195
196 # Create the source tarball if not already there
197 if [ ! -e "$GIT_ARCHIVE" ]; then
198 mkdir -p "$(dirname "$GIT_ARCHIVE")"
199 REFERENCE_DATETIME="@${SOURCE_DATE_EPOCH}" \
200 contrib/guix/libexec/make_release_tarball.sh "${GIT_ARCHIVE}" "${DISTNAME}"
201 fi
202
203 mkdir -p "$OUTDIR"
204
205 ###########################
206 # Binary Tarball Building #
207 ###########################
208
209 # CONFIGFLAGS
210 CONFIGFLAGS="-DREDUCE_EXPORTS=ON -DBUILD_BENCH=OFF -DBUILD_GUI_TESTS=OFF -DBUILD_FUZZ_BINARY=OFF"
211 CONFIGFLAGS="$CONFIGFLAGS -DCMAKE_SKIP_BUILD_RPATH=TRUE" # check-symbols is fussy about rpath and we don't need it
212 CONFIGFLAGS="$CONFIGFLAGS -D RDTS_CONSENT=RUNTIME_WARN"
213
214 # CFLAGS
215 HOST_CFLAGS="-O2 -g"
216 HOST_CFLAGS+=$(find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;)
217 case "$HOST" in
218 *mingw*) HOST_CFLAGS+=" -fno-ident" ;;
219 *darwin*) unset HOST_CFLAGS ;;
220 esac
221
222 # CXXFLAGS
223 HOST_CXXFLAGS="$HOST_CFLAGS"
224
225 case "$HOST" in
226 arm-linux-gnueabihf) HOST_CXXFLAGS="${HOST_CXXFLAGS} -Wno-psabi" ;;
227 esac
228
229 # LDFLAGS
230 case "$HOST" in
231 *linux*) HOST_LDFLAGS="-Wl,--as-needed -Wl,--dynamic-linker=$glibc_dynamic_linker -static-libstdc++ -Wl,-O2" ;;
232 *mingw*) HOST_LDFLAGS="-Wl,--no-insert-timestamp" ;;
233 *darwin*) HOST_LDFLAGS="-Wl,--icf=safe" ;;
234 esac
235
236 mkdir -p "$DISTSRC"
237 (
238 cd "$DISTSRC"
239
240 # Extract the source tarball
241 tar --strip-components=1 -xf "${GIT_ARCHIVE}"
242
243 # First build liblimenkaconsensus
244 # shellcheck disable=SC2086
245 env CFLAGS="${HOST_CFLAGS}" CXXFLAGS="${HOST_CXXFLAGS}" LDFLAGS="${HOST_LDFLAGS}" \
246 cmake -S . -B build_liblimenkaconsensus \
247 --toolchain "${BASEPREFIX}/${HOST}/toolchain.cmake" \
248 -DWITH_CCACHE=OFF \
249 ${CONFIGFLAGS} \
250 -DBUILD_BENCH=OFF \
251 -DBUILD_CLI=OFF \
252 -DBUILD_DAEMON=OFF \
253 -DBUILD_FOR_FUZZING=OFF \
254 -DBUILD_FUZZ_BINARY=OFF \
255 -DBUILD_GUI=OFF \
256 -DBUILD_GUI_TESTS=OFF \
257 -DBUILD_KERNEL_LIB=OFF \
258 -DBUILD_TESTS=OFF \
259 -DBUILD_TX=OFF \
260 -DBUILD_UTIL=OFF \
261 -DBUILD_UTIL_CHAINSTATE=OFF \
262 -DBUILD_WALLET_TOOL=OFF \
263 -DBUILD_SHARED_LIBS=ON -DBUILD_LIMENKACONSENSUS_LIB=ON
264 cmake --build build_liblimenkaconsensus -j "$JOBS" ${V:+--verbose}
265 cmake --build build_liblimenkaconsensus -j 1 --target check-security ${V:+--verbose}
266 cmake --build build_liblimenkaconsensus -j 1 --target check-symbols ${V:+--verbose}
267
268 # Configure this DISTSRC for $HOST
269 # shellcheck disable=SC2086
270 env CFLAGS="${HOST_CFLAGS}" CXXFLAGS="${HOST_CXXFLAGS}" LDFLAGS="${HOST_LDFLAGS}" \
271 cmake -S . -B build \
272 --toolchain "${BASEPREFIX}/${HOST}/toolchain.cmake" \
273 -DWITH_CCACHE=OFF \
274 ${CONFIGFLAGS}
275
276 # Build Limenka
277 cmake --build build -j "$JOBS" ${V:+--verbose}
278
279 # Perform basic security checks on a series of executables.
280 cmake --build build -j 1 --target check-security ${V:+--verbose}
281 # Check that executables only contain allowed version symbols.
282 cmake --build build -j 1 --target check-symbols ${V:+--verbose}
283
284 mkdir -p "$OUTDIR"
285
286 # Make the os-specific installers
287 case "$HOST" in
288 *mingw*)
289 cmake --build build -j "$JOBS" -t deploy ${V:+--verbose}
290 mv build/limenka-win64-setup.exe "${OUTDIR}/${DISTNAME}-win64-setup-pgpverifiable.exe"
291 ;;
292 esac
293
294 # Setup the directory where our Limenka build for HOST will be
295 # installed. This directory will also later serve as the input for our
296 # binary tarballs.
297 INSTALLPATH="${PWD}/installed/${DISTNAME}"
298 mkdir -p "${INSTALLPATH}"
299 # Install built Limenka to $INSTALLPATH
300 case "$HOST" in
301 *darwin*)
302 # This workaround can be dropped for CMake >= 3.27.
303 # See the upstream commit 689616785f76acd844fd448c51c5b2a0711aafa2.
304 find build* -name 'cmake_install.cmake' -exec sed -i 's| -u -r | |g' {} +
305
306 cmake --install build_liblimenkaconsensus --strip --prefix "${INSTALLPATH}" ${V:+--verbose}
307 cmake --install build --strip --prefix "${INSTALLPATH}" ${V:+--verbose}
308 ;;
309 *)
310 cmake --install build_liblimenkaconsensus --prefix "${INSTALLPATH}" ${V:+--verbose}
311 cmake --install build --prefix "${INSTALLPATH}" ${V:+--verbose}
312 ;;
313 esac
314
315 (
316 cd installed
317
318 case "$HOST" in
319 *darwin*) ;;
320 *)
321 # Split binaries from their debug symbols
322 {
323 find "${DISTNAME}/bin" -type f -executable -print0
324 if test -d "${DISTNAME}/lib"; then
325 find "${DISTNAME}/lib" -type f -executable -print0
326 fi
327 } | xargs -0 -P"$JOBS" -I{} "${DISTSRC}/build/split-debug.sh" {} {} {}.dbg
328 ;;
329 esac
330
331 case "$HOST" in
332 *mingw*)
333 cp "${DISTSRC}/doc/README_windows.txt" "${DISTNAME}/readme.txt"
334 ;;
335 *linux*)
336 cp "${DISTSRC}/README.md" "${DISTNAME}/"
337 ;;
338 esac
339
340 # copy over the example limenka.conf file. if contrib/devtools/gen-limenka-conf.sh
341 # has not been run before buildling, this file will be a stub
342 cp "${DISTSRC}/share/examples/limenka.conf" "${DISTNAME}/"
343
344 cp -r "${DISTSRC}/share/rpcauth" "${DISTNAME}/share/"
345
346 # Deterministically produce {non-,}debug binary tarballs ready
347 # for release
348 case "$HOST" in
349 *mingw*)
350 find "${DISTNAME}" -not -name "*.dbg" -print0 \
351 | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
352 find "${DISTNAME}" -not -name "*.dbg" \
353 | sort \
354 | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-pgpverifiable.zip" \
355 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-pgpverifiable.zip" && exit 1 )
356 find "${DISTNAME}" -name "*.dbg" -print0 \
357 | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
358 find "${DISTNAME}" -name "*.dbg" \
359 | sort \
360 | zip -X@ "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-debug.zip" \
361 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-w64-mingw32/win64}-debug.zip" && exit 1 )
362 ;;
363 *linux*)
364 find "${DISTNAME}" -not -name "*.dbg" -print0 \
365 | sort --zero-terminated \
366 | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
367 | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \
368 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 )
369 find "${DISTNAME}" -name "*.dbg" -print0 \
370 | sort --zero-terminated \
371 | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
372 | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" \
373 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" && exit 1 )
374 ;;
375 *darwin*)
376 find "${DISTNAME}" -print0 \
377 | sort --zero-terminated \
378 | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
379 | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \
380 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 )
381 ;;
382 esac
383 ) # $DISTSRC/installed
384
385 # Finally make tarballs for codesigning
386 case "$HOST" in
387 *mingw*)
388 cp -rf --target-directory=. contrib/windeploy
389 (
390 cd ./windeploy
391 mkdir -p unsigned
392 cp --target-directory=unsigned/ "${OUTDIR}/${DISTNAME}-win64-setup-pgpverifiable.exe"
393 cp -r --target-directory=unsigned/ "${INSTALLPATH}"
394 find unsigned/ -name "*.dbg" -print0 \
395 | xargs -0r rm
396 find . -print0 \
397 | sort --zero-terminated \
398 | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
399 | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" \
400 || ( rm -f "${OUTDIR}/${DISTNAME}-win64-codesigning.tar.gz" && exit 1 )
401 )
402 ;;
403 *darwin*)
404 cmake --build build --target deploy ${V:+--verbose}
405 mv build/dist/*.zip "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.zip"
406 mkdir -p "unsigned-app-${HOST}"
407 cp --target-directory="unsigned-app-${HOST}" \
408 contrib/macdeploy/detached-sig-create.sh
409 mv --target-directory="unsigned-app-${HOST}" build/dist
410 cp -r --target-directory="unsigned-app-${HOST}" "${INSTALLPATH}"
411 (
412 cd "unsigned-app-${HOST}"
413 find . -print0 \
414 | sort --zero-terminated \
415 | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \
416 | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" \
417 || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-codesigning.tar.gz" && exit 1 )
418 )
419 ;;
420 esac
421 ) # $DISTSRC
422
423 rm -rf "$ACTUAL_OUTDIR"
424 mv --no-target-directory "$OUTDIR" "$ACTUAL_OUTDIR" \
425 || ( rm -rf "$ACTUAL_OUTDIR" && exit 1 )
426
427 (
428 cd /outdir-base
429 {
430 echo "$GIT_ARCHIVE"
431 find "$ACTUAL_OUTDIR" -type f
432 } | xargs realpath --relative-base="$PWD" \
433 | xargs sha256sum \
434 | sort -k2 \
435 | sponge "$ACTUAL_OUTDIR"/SHA256SUMS.part
436 )
437