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