/ scripts / release.sh
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