diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh new file mode 100644 index 0000000..561cbb5 --- /dev/null +++ b/ci/before_deploy.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# package the build artifacts + +set -ex + +. "$(dirname $0)/utils.sh" + +# Generate artifacts for release +mk_artifacts() { + CARGO="$(builder)" + if is_arm; then + "$CARGO" build --target "$TARGET" --release + else + # Technically, MUSL builds will force PCRE2 to get statically compiled, + # but we also want PCRE2 statically build for macOS binaries. + PCRE2_SYS_STATIC=1 "$CARGO" build --target "$TARGET" --release --features 'pcre2' + fi +} + +mk_tarball() { + # When cross-compiling, use the right `strip` tool on the binary. + local gcc_prefix="$(gcc_prefix)" + # Create a temporary dir that contains our staging area. + # $tmpdir/$name is what eventually ends up as the deployed archive. + local tmpdir="$(mktemp -d)" + local name="${PROJECT_NAME}-${TRAVIS_TAG}-${TARGET}" + local staging="$tmpdir/$name" + mkdir -p "$staging"/{complete,doc} + # The deployment directory is where the final archive will reside. + # This path is known by the .travis.yml configuration. + local out_dir="$(pwd)/deployment" + mkdir -p "$out_dir" + # Find the correct (most recent) Cargo "out" directory. The out directory + # contains shell completion files and the man page. + local cargo_out_dir="$(cargo_out_dir "target/$TARGET")" + + # Copy the ripgrep binary and strip it. + cp "target/$TARGET/release/rg" "$staging/rg" + "${gcc_prefix}strip" "$staging/rg" + # Copy the licenses and README. + cp {README.md,UNLICENSE,COPYING,LICENSE-MIT} "$staging/" + # Copy documentation and man page. + cp {CHANGELOG.md,FAQ.md,GUIDE.md} "$staging/doc/" + if command -V a2x 2>&1 > /dev/null; then + # The man page should only exist if we have asciidoc installed. + cp "$cargo_out_dir/rg.1" "$staging/doc/" + fi + # Copy shell completion files. + cp "$cargo_out_dir"/{rg.bash,rg.fish,_rg.ps1} "$staging/complete/" + cp complete/_rg "$staging/complete/" + + (cd "$tmpdir" && tar czf "$out_dir/$name.tar.gz" "$name") + rm -rf "$tmpdir" +} + +main() { + mk_artifacts + mk_tarball +} + +main diff --git a/ci/build_deb.sh b/ci/build_deb.sh new file mode 100755 index 0000000..4812b47 --- /dev/null +++ b/ci/build_deb.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -e + +# This script builds a binary dpkg for Debian based distros. It does not +# currently run in CI, and is instead run manually and the resulting dpkg is +# uploaded to GitHub via the web UI. +# +# Note that this requires 'cargo deb', which can be installed with +# 'cargo install cargo-deb'. +# +# This should be run from the root of the ripgrep repo. + +if ! command -V cargo-deb > /dev/null 2>&1; then + echo "cargo-deb command missing" >&2 + exit 1 +fi + +# 'cargo deb' does not seem to provide a way to specify an asset that is +# created at build time, such as ripgrep's man page. To work around this, +# we force a debug build, copy out the man page (and shell completions) +# produced from that build, put it into a predictable location and then build +# the deb, which knows where to look. + +DEPLOY_DIR=deployment/deb +mkdir -p "$DEPLOY_DIR" +cargo build + +# Find and copy man page. +manpage="$(find ./target/debug -name rg.1 -print0 | xargs -0 ls -t | head -n1)" +cp "$manpage" "$DEPLOY_DIR/" + +# Do the same for shell completions. +compbash="$(find ./target/debug -name rg.bash -print0 | xargs -0 ls -t | head -n1)" +cp "$compbash" "$DEPLOY_DIR/" +compfish="$(find ./target/debug -name rg.fish -print0 | xargs -0 ls -t | head -n1)" +cp "$compfish" "$DEPLOY_DIR/" +compzsh="complete/_rg" +cp "$compzsh" "$DEPLOY_DIR/" + +# Since we're distributing the dpkg, we don't know whether the user will have +# PCRE2 installed, so just do a static build. +PCRE2_SYS_STATIC=1 cargo deb diff --git a/ci/install.sh b/ci/install.sh new file mode 100755 index 0000000..37af9d7 --- /dev/null +++ b/ci/install.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# install stuff needed for the `script` phase + +# Where rustup gets installed. +export PATH="$PATH:$HOME/.cargo/bin" + +set -ex + +. "$(dirname $0)/utils.sh" + +install_rustup() { + curl https://sh.rustup.rs -sSf \ + | sh -s -- -y --default-toolchain="$TRAVIS_RUST_VERSION" + rustc -V + cargo -V +} + +install_targets() { + if [ $(host) != "$TARGET" ]; then + rustup target add $TARGET + fi +} + +install_osx_dependencies() { + if ! is_osx; then + return + fi + + brew install asciidoc docbook-xsl +} + +configure_cargo() { + local prefix=$(gcc_prefix) + if [ -n "${prefix}" ]; then + local gcc_suffix= + if [ -n "$GCC_VERSION" ]; then + gcc_suffix="-$GCC_VERSION" + fi + local gcc="${prefix}gcc${gcc_suffix}" + + # information about the cross compiler + "${gcc}" -v + + # tell cargo which linker to use for cross compilation + mkdir -p .cargo + cat >>.cargo/config <&2 + exit 1 +fi +version="$1" + +# Linux and Darwin builds. +for arch in i686 x86_64; do + for target in apple-darwin unknown-linux-musl; do + url="https://github.com/BurntSushi/ripgrep/releases/download/$version/ripgrep-$version-$arch-$target.tar.gz" + sha=$(curl -sfSL "$url" | sha256sum) + echo "$version-$arch-$target $sha" + done +done + +# Source. +for ext in zip tar.gz; do + url="https://github.com/BurntSushi/ripgrep/archive/$version.$ext" + sha=$(curl -sfSL "$url" | sha256sum) + echo "source.$ext $sha" +done diff --git a/ci/test_complete.sh b/ci/test_complete.sh new file mode 100755 index 0000000..985ef11 --- /dev/null +++ b/ci/test_complete.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env zsh + +emulate zsh -o extended_glob -o no_function_argzero -o no_unset + +## +# Compares options in `rg --help` output to options in zsh completion function + +get_comp_args() { + # Technically there are many options that the completion system sets that + # our function may rely on, but we'll trust that we've got it mostly right + setopt local_options unset + + # Our completion function recognises a special variable which tells it to + # dump the _arguments specs and then just return. But do this in a sub-shell + # anyway to avoid any weirdness + ( _RG_COMPLETE_LIST_ARGS=1 source $1 ) +} + +main() { + local diff + local rg="${0:a:h}/../target/${TARGET:-}/release/rg" + local _rg="${0:a:h}/../complete/_rg" + local -a help_args comp_args + + [[ -e $rg ]] || rg=${rg/%\/release\/rg/\/debug\/rg} + + rg=${rg:a} + _rg=${_rg:a} + + [[ -e $rg ]] || { + print -r >&2 "File not found: $rg" + return 1 + } + [[ -e $_rg ]] || { + print -r >&2 "File not found: $_rg" + return 1 + } + + print -rl - 'Comparing options:' "-$rg" "+$_rg" + + # 'Parse' options out of the `--help` output. To prevent false positives we + # only look at lines where the first non-white-space character is `-`, or + # where a long option starting with certain letters (see `_rg`) is found. + # Occasionally we may have to handle some manually, however + help_args=( ${(f)"$( + $rg --help | + $rg -i -- '^\s+--?[a-z0-9]|--[imnp]' | + $rg -ior '$1' -- $'[\t /\"\'`.,](-[a-z0-9]|--[a-z0-9-]+)\\b' | + $rg -v -- --print0 | # False positives + sort -u + )"} ) + + # 'Parse' options out of the completion function + comp_args=( ${(f)"$( get_comp_args $_rg )"} ) + + # Note that we currently exclude hidden (!...) options; matching these + # properly against the `--help` output could be irritating + comp_args=( ${comp_args#\(*\)} ) # Strip excluded options + comp_args=( ${comp_args#\*} ) # Strip repetition indicator + comp_args=( ${comp_args%%-[:[]*} ) # Strip everything after -optname- + comp_args=( ${comp_args%%[:+=[]*} ) # Strip everything after other optspecs + comp_args=( ${comp_args##[^-]*} ) # Remove non-options + comp_args=( ${(f)"$( print -rl - $comp_args | sort -u )"} ) + + (( $#help_args )) || { + print -r >&2 'Failed to get help_args' + return 1 + } + (( $#comp_args )) || { + print -r >&2 'Failed to get comp_args' + return 1 + } + + diff="$( + if diff --help 2>&1 | grep -qF -- '--label'; then + diff -U2 \ + --label '`rg --help`' \ + --label '`_rg`' \ + =( print -rl - $help_args ) =( print -rl - $comp_args ) + else + diff -U2 \ + -L '`rg --help`' \ + -L '`_rg`' \ + =( print -rl - $help_args ) =( print -rl - $comp_args ) + fi + )" + + (( $#diff )) && { + printf >&2 '%s\n' 'zsh completion options differ from `--help` options:' + printf >&2 '%s\n' $diff + return 1 + } + printf 'OK\n' + return 0 +} + +main "$@" diff --git a/ci/utils.sh b/ci/utils.sh new file mode 100644 index 0000000..f3dc96d --- /dev/null +++ b/ci/utils.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# Various utility functions used through CI. + +# Finds Cargo's `OUT_DIR` directory from the most recent build. +# +# This requires one parameter corresponding to the target directory +# to search for the build output. +cargo_out_dir() { + # This works by finding the most recent stamp file, which is produced by + # every ripgrep build. + target_dir="$1" + find "$target_dir" -name ripgrep-stamp -print0 \ + | xargs -0 ls -t \ + | head -n1 \ + | xargs dirname +} + +host() { + case "$TRAVIS_OS_NAME" in + linux) + echo x86_64-unknown-linux-gnu + ;; + osx) + echo x86_64-apple-darwin + ;; + esac +} + +architecture() { + case "$TARGET" in + x86_64-*) + echo amd64 + ;; + i686-*|i586-*|i386-*) + echo i386 + ;; + arm*-unknown-linux-gnueabihf) + echo armhf + ;; + *) + die "architecture: unexpected target $TARGET" + ;; + esac +} + +gcc_prefix() { + case "$(architecture)" in + armhf) + echo arm-linux-gnueabihf- + ;; + *) + return + ;; + esac +} + +is_musl() { + case "$TARGET" in + *-musl) return 0 ;; + *) return 1 ;; + esac +} + +is_x86() { + case "$(architecture)" in + amd64|i386) return 0 ;; + *) return 1 ;; + esac +} + +is_x86_64() { + case "$(architecture)" in + amd64) return 0 ;; + *) return 1 ;; + esac +} + +is_arm() { + case "$(architecture)" in + armhf) return 0 ;; + *) return 1 ;; + esac +} + +is_linux() { + case "$TRAVIS_OS_NAME" in + linux) return 0 ;; + *) return 1 ;; + esac +} + +is_osx() { + case "$TRAVIS_OS_NAME" in + osx) return 0 ;; + *) return 1 ;; + esac +} + +builder() { + if is_musl && is_x86_64; then + cargo install cross + echo "cross" + else + echo "cargo" + fi +}