release.sh
1 #!/bin/bash 2 3 # Simple bash script to build basic lnd tools for all the platforms 4 # we support with the golang cross-compiler. 5 # 6 # Copyright (c) 2016 Company 0, LLC. 7 # Use of this source code is governed by the ISC 8 # license. 9 10 set -e 11 12 LND_VERSION_REGEX="lnd version (.+) commit" 13 PKG="github.com/lightningnetwork/lnd" 14 PACKAGE=lnd 15 16 # Needed for setting file timestamps to get reproducible archives. 17 BUILD_DATE="2020-01-01 00:00:00" 18 BUILD_DATE_STAMP="202001010000.00" 19 20 # reproducible_tar_gzip creates a reproducible tar.gz file of a directory. This 21 # includes setting all file timestamps and ownership settings uniformly. 22 function reproducible_tar_gzip() { 23 local dir=$1 24 local tar_cmd=tar 25 local gzip_cmd=gzip 26 27 # MacOS has a version of BSD tar which doesn't support setting the --mtime 28 # flag. We need gnu-tar, or gtar for short to be installed for this script to 29 # work properly. 30 tar_version=$(tar --version) 31 if [[ ! "$tar_version" =~ "GNU tar" ]]; then 32 if ! command -v "gtar"; then 33 echo "GNU tar is required but cannot be found!" 34 echo "On MacOS please run 'brew install gnu-tar' to install gtar." 35 exit 1 36 fi 37 38 # We have gtar installed, use that instead. 39 tar_cmd=gtar 40 fi 41 42 # On MacOS, the default BSD gzip produces a different output than the GNU 43 # gzip on Linux. To ensure reproducible builds, we need to use GNU gzip. 44 gzip_version=$(gzip --version 2>&1 || true) 45 if [[ ! "$gzip_version" =~ "GNU" ]]; then 46 if ! command -v "ggzip" >/dev/null 2>&1; then 47 echo "GNU gzip is required but cannot be found!" 48 echo "On MacOS please run 'brew install gzip' to install ggzip." 49 exit 1 50 fi 51 52 # We have ggzip installed, use that instead. 53 gzip_cmd=ggzip 54 fi 55 56 # Pin down the timestamp time zone. 57 export TZ=UTC 58 59 find "${dir}" -print0 | LC_ALL=C sort -r -z | $tar_cmd \ 60 "--mtime=${BUILD_DATE}" --no-recursion --null --mode=u+rw,go+r-w,a+X \ 61 --owner=0 --group=0 --numeric-owner -c -T - | $gzip_cmd \ 62 -9n > "${dir}.tar.gz" 63 64 rm -r "${dir}" 65 } 66 67 # reproducible_zip creates a reproducible zip file of a directory. This 68 # includes setting all file timestamps. 69 function reproducible_zip() { 70 local dir=$1 71 72 # Pin down file name encoding and timestamp time zone. 73 export TZ=UTC 74 75 # Set the date of each file in the directory that's about to be packaged to 76 # the same timestamp and make sure the same permissions are used everywhere. 77 chmod -R 0755 "${dir}" 78 touch -t "${BUILD_DATE_STAMP}" "${dir}" 79 find "${dir}" -print0 | LC_ALL=C sort -r -z | xargs -0r touch \ 80 -t "${BUILD_DATE_STAMP}" 81 82 find "${dir}" | LC_ALL=C sort -r | zip -o -X -r -@ "${dir}.zip" 83 84 rm -r "${dir}" 85 } 86 87 # green prints one line of green text (if the terminal supports it). 88 function green() { 89 echo -e "\e[0;32m${1}\e[0m" 90 } 91 92 # red prints one line of red text (if the terminal supports it). 93 function red() { 94 echo -e "\e[0;31m${1}\e[0m" 95 } 96 97 # check_tag_correct makes sure the given git tag is checked out and the git tree 98 # is not dirty. 99 # arguments: <version-tag> 100 function check_tag_correct() { 101 local tag=$1 102 103 # For automated builds we can skip this check as they will only be triggered 104 # on tags. 105 if [[ "$SKIP_VERSION_CHECK" -eq "1" ]]; then 106 green "skipping version check, assuming automated build" 107 exit 0 108 fi 109 110 # If a tag is specified, ensure that that tag is present and checked out. 111 if [[ $tag != $(git describe --tags) ]]; then 112 red "tag $tag not checked out" 113 exit 1 114 fi 115 116 # Build lnd to extract version. 117 go build ${PKG}/cmd/lnd 118 119 # Extract version command output. 120 lnd_version_output=$(./lnd --version) 121 122 # Use a regex to isolate the version string. 123 if [[ $lnd_version_output =~ $LND_VERSION_REGEX ]]; then 124 # Prepend 'v' to match git tag naming scheme. 125 lnd_version="v${BASH_REMATCH[1]}" 126 green "version: $lnd_version" 127 128 # If tag contains a release candidate suffix, append this suffix to the 129 # lnd reported version before we compare. 130 RC_REGEX="-rc[0-9]+$" 131 if [[ $tag =~ $RC_REGEX ]]; then 132 lnd_version+=${BASH_REMATCH[0]} 133 fi 134 135 # Match git tag with lnd version. 136 if [[ $tag != "${lnd_version}" ]]; then 137 red "lnd version $lnd_version does not match tag $tag" 138 exit 1 139 fi 140 else 141 red "malformed lnd version output" 142 exit 1 143 fi 144 } 145 146 # build_release builds the actual release binaries. 147 # arguments: <version-tag> <build-system(s)> <build-tags> <ldflags> 148 # <go-version> 149 function build_release() { 150 local tag=$1 151 local sys=$2 152 local buildtags=$3 153 local ldflags=$4 154 local goversion=$5 155 156 # Check if the active Go version matches the specified Go version. 157 active_go_version=$(go version | awk '{print $3}' | sed 's/go//') 158 if [ "$active_go_version" != "$goversion" ]; then 159 echo "Error: active Go version ($active_go_version) does not match \ 160 required Go version ($goversion)." 161 exit 1 162 fi 163 164 echo "Building release for tag $tag with Go version $goversion" 165 166 green " - Packaging vendor" 167 go mod vendor 168 reproducible_tar_gzip vendor 169 170 maindir=$PACKAGE-$tag 171 mkdir -p $maindir 172 mv vendor.tar.gz "${maindir}/" 173 174 # Don't use tag in source directory, otherwise our file names get too long and 175 # tar starts to package them non-deterministically. 176 package_source="${PACKAGE}-source" 177 178 # The git archive command doesn't support setting timestamps and file 179 # permissions. That's why we unpack the tar again, then use our reproducible 180 # method to create the final archive. 181 git archive -o "${maindir}/${package_source}.tar" HEAD 182 183 cd "${maindir}" 184 mkdir -p ${package_source} 185 tar -xf "${package_source}.tar" -C ${package_source} 186 rm "${package_source}.tar" 187 reproducible_tar_gzip ${package_source} 188 mv "${package_source}.tar.gz" "${package_source}-$tag.tar.gz" 189 190 for i in $sys; do 191 os=$(echo $i | cut -f1 -d-) 192 arch=$(echo $i | cut -f2 -d-) 193 arm= 194 195 if [[ $arch == "armv6" ]]; then 196 arch=arm 197 arm=6 198 elif [[ $arch == "armv7" ]]; then 199 arch=arm 200 arm=7 201 fi 202 203 dir="${PACKAGE}-${i}-${tag}" 204 mkdir "${dir}" 205 pushd "${dir}" 206 207 green " - Building: ${os} ${arch} ${arm} with build tags '${buildtags}'" 208 env CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${buildtags}" ${PKG}/cmd/lnd 209 env CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${buildtags}" ${PKG}/cmd/lncli 210 popd 211 212 # Clear Go build cache to prevent disk space issues during multi-platform builds. 213 go clean -cache 214 215 # Add the hashes for the individual binaries as well for easy verification 216 # of a single installed binary. 217 shasum -a 256 "${dir}/"* >> "manifest-$tag.txt" 218 219 if [[ $os == "windows" ]]; then 220 reproducible_zip "${dir}" 221 else 222 reproducible_tar_gzip "${dir}" 223 fi 224 done 225 226 # Add the hash of the packages too, then sort by the second column (name). 227 shasum -a 256 lnd-* vendor* >> "manifest-$tag.txt" 228 LC_ALL=C sort -k2 -o "manifest-$tag.txt" "manifest-$tag.txt" 229 cat "manifest-$tag.txt" 230 } 231 232 # usage prints the usage of the whole script. 233 function usage() { 234 red "Usage: " 235 red "release.sh check-tag <version-tag>" 236 red "release.sh build-release <version-tag> <build-system(s)> <build-tags> <ldflags> <go-version>" 237 } 238 239 # Whatever sub command is passed in, we need at least 2 arguments. 240 if [ "$#" -lt 2 ]; then 241 usage 242 exit 1 243 fi 244 245 # Extract the sub command and remove it from the list of parameters by shifting 246 # them to the left. 247 SUBCOMMAND=$1 248 shift 249 250 # Call the function corresponding to the specified sub command or print the 251 # usage if the sub command was not found. 252 case $SUBCOMMAND in 253 check-tag) 254 green "Checking if version tag exists" 255 check_tag_correct "$@" 256 ;; 257 build-release) 258 green "Building release" 259 build_release "$@" 260 ;; 261 *) 262 usage 263 exit 1 264 ;; 265 esac