Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/ci/build-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
#
# Build a Docker project end-to-end: create it with install.sh --env docker
# (or take a pre-created one as $1) and build it inside the devx devcontainer
# image — the same image devcontainers, Codespaces and the standalone
# `docker run` flow use.
#
# The image is x86_64-linux; on other architectures docker's emulation is
# used automatically (slow but correct).
#
# When docker is unavailable the script SKIPS (exit 0) with a loud notice,
# unless PLINTH_REQUIRE_DOCKER=1 (set in CI) makes that a failure.
#
# Environment:
# PLINTH_REQUIRE_DOCKER=1 Fail instead of skipping when docker is missing.
# PLINTH_CI_KEEP=1 Keep the scratch directory (printed).

set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"

IMAGE="ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog"
TREE="${1:-}"

fail() { echo "build-docker: FAIL: $*" >&2; exit 1; }
note() { echo "build-docker: $*"; }

if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
if [ "${PLINTH_REQUIRE_DOCKER:-0}" = 1 ]; then
fail "docker is not available (PLINTH_REQUIRE_DOCKER=1)"
fi
echo "build-docker: SKIPPED — docker is not installed or its daemon is not running." >&2
echo "build-docker: start docker and re-run, or rely on CI for this branch." >&2
exit 0
fi

WORK="$(mktemp -d "${TMPDIR:-/tmp}/plinth-build-docker.XXXXXX")"

# The container builds as root, so dist-newstyle/ ends up owned by root on
# the host and a plain rm cannot remove it. Hand the files back through a
# throwaway container before removing them, and never let a cleanup problem
# fail an otherwise successful build.
cleanup() {
if [ "${PLINTH_CI_KEEP:-0}" = 1 ]; then
echo "build-docker: scratch dir kept: $WORK"
return 0
fi
if rm -rf "$WORK" 2>/dev/null; then
return 0
fi
docker run --rm -v "$WORK:/scratch" "$IMAGE" \
chown -R "$(id -u):$(id -g)" /scratch >/dev/null 2>&1 || true
if ! rm -rf "$WORK"; then
echo "build-docker: warning: could not remove the scratch dir $WORK" >&2
fi
}
trap cleanup EXIT

PROJECT="$WORK/project"
if [ -z "$TREE" ]; then
if ! sh "$ROOT/install.sh" --yes --env docker --docker-mode standalone --from "$ROOT" \
--dir "$PROJECT" >"$WORK/install.log" 2>&1; then
cat "$WORK/install.log" >&2
fail "install.sh --env docker failed"
fi
else
mkdir -p "$PROJECT"
cp -R "$TREE/." "$PROJECT/"
fi
if [ ! -f "$PROJECT/.devcontainer/devcontainer.json" ]; then
fail "$PROJECT is not a docker project"
fi

# `bash -ic` is required: the devx image loads the nix toolchain environment
# from ~/.bashrc, which only interactive shells source.
note "building inside $IMAGE ..."
if ! docker run --rm \
-v "$PROJECT:/workspaces/plinth-template" \
-w /workspaces/plinth-template \
-i "$IMAGE" \
bash -ic '
set -euo pipefail
echo "build-docker(container): ghc $(ghc --numeric-version) at $(command -v ghc)"
if ! pkg-config --exists libsodium libsecp256k1 libblst; then
echo "build-docker(container): FAIL: crypto libs not provided by the image" >&2
exit 1
fi
echo "build-docker(container): crypto libs provided by the image (via nix)"
cabal update
cabal build all
cabal run -v0 exe:gen-auction-validator-blueprint -- blueprint.json
if [ ! -s blueprint.json ]; then
echo "build-docker(container): FAIL: empty blueprint" >&2
exit 1
fi
echo "build-docker(container): blueprint OK"
'; then
fail "container build failed"
fi

echo "build-docker: SUCCESS"
152 changes: 152 additions & 0 deletions .github/ci/build-ghc-cabal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
#
# Build a GHC+Cabal project end-to-end with the host's ghc+cabal: create the
# project with install.sh --env cabal --crypto-libs local (or take a
# pre-created one as $1), which downloads the crypto C libraries into the
# per-user cache, then run `cabal build all` with the generated env.sh
# sourced (it points PKG_CONFIG_PATH at the libraries), generate the example
# blueprint and assert the produced executable links the crypto libraries
# from the plinth cache.
#
# Requirements: ghc 9.6.x or 9.12.x, cabal >= 3.8, pkg-config, network.
#
# Environment:
# CABAL_STORE_DIR Use a dedicated cabal store (passed as --store-dir);
# speeds up repeated local runs enormously.
# PLINTH_CI_KEEP=1 Keep the scratch directory (printed) for inspection.

set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"

TREE="${1:-}"

WORK="$(mktemp -d "${TMPDIR:-/tmp}/plinth-build-cabal.XXXXXX")"
cleanup() {
if [ "${PLINTH_CI_KEEP:-0}" = 1 ]; then
echo "build-ghc-cabal: scratch dir kept: $WORK"
else
rm -rf "$WORK"
fi
}
trap cleanup EXIT

fail() { echo "build-ghc-cabal: FAIL: $*" >&2; exit 1; }
note() { echo "build-ghc-cabal: $*"; }

# --------------------------------------------------------------------------
# Toolchain assertions (same requirements install.sh enforces)
# --------------------------------------------------------------------------

if ! command -v ghc >/dev/null 2>&1; then
fail "ghc not on PATH"
fi
if ! command -v cabal >/dev/null 2>&1; then
fail "cabal not on PATH"
fi
if ! command -v pkg-config >/dev/null 2>&1; then
fail "pkg-config not on PATH"
fi
GHC_VERSION="$(ghc --numeric-version)"
case "$GHC_VERSION" in
9.6.*|9.12.*) note "ghc $GHC_VERSION ($(command -v ghc))" ;;
*) fail "unsupported ghc $GHC_VERSION (need 9.6.x or 9.12.x)" ;;
esac
note "cabal $(cabal --numeric-version) ($(command -v cabal))"

# --------------------------------------------------------------------------
# Fresh copy of the branch tree
# --------------------------------------------------------------------------

PROJECT="$WORK/project"
if [ -z "$TREE" ]; then
if ! sh "$ROOT/install.sh" --yes --env cabal --from "$ROOT" \
--dir "$PROJECT" --crypto-libs local >"$WORK/install.log" 2>&1; then
cat "$WORK/install.log" >&2
fail "install.sh --env cabal failed"
fi
else
mkdir -p "$PROJECT"
cp -R "$TREE/." "$PROJECT/"
fi
if [ ! -f "$PROJECT/get-crypto-libs.sh" ]; then
fail "$PROJECT is not a ghc-cabal project"
fi
cd "$PROJECT"

# install.sh (--crypto-libs local) already ran this; re-running is an
# instant no-op re-link. For a pre-created tree ($1) it does the install.
if ! ./get-crypto-libs.sh; then
fail "get-crypto-libs.sh failed"
fi

# Source the generated env.sh so pkg-config resolves the libraries (this is
# exactly what the README tells users to do).
if [ ! -f dist-newstyle/crypto-libs/env.sh ]; then
fail "dist-newstyle/crypto-libs/env.sh was not created by get-crypto-libs.sh"
fi
# shellcheck source=/dev/null
. dist-newstyle/crypto-libs/env.sh
if ! pkg-config --exists libsodium libsecp256k1 libblst; then
fail "crypto libs not visible to pkg-config after sourcing env.sh"
fi
note "crypto libs: sodium $(pkg-config --modversion libsodium), secp256k1 $(pkg-config --modversion libsecp256k1), blst $(pkg-config --modversion libblst)"

cabal=(cabal)
if [ -n "${CABAL_STORE_DIR:-}" ]; then
mkdir -p "$CABAL_STORE_DIR"
cabal+=(--store-dir="$CABAL_STORE_DIR")
note "using cabal store: $CABAL_STORE_DIR"
fi

if [ -n "${CI:-}" ]; then
note "CI: running cabal update"
"${cabal[@]}" update
fi

note "building (cabal build all)..."
"${cabal[@]}" build all

# --------------------------------------------------------------------------
# Blueprint generation + provenance of the linked crypto libraries
# --------------------------------------------------------------------------

note "generating the example blueprint..."
"${cabal[@]}" run -v0 exe:gen-auction-validator-blueprint -- "$WORK/blueprint.json"
if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$WORK/blueprint.json"; then
fail "blueprint output is not valid JSON"
fi
note "blueprint OK: $(wc -c < "$WORK/blueprint.json" | tr -d ' ') bytes"

# The libraries' real location is the per-user cache; PLINTH_CRYPTO_LIBS_HOME
# overrides it (get-crypto-libs.sh defaults to .../plinth-crypto-libs).
CRYPTO_MARKER="${PLINTH_CRYPTO_LIBS_HOME:-plinth-crypto-libs}"

bin="$("${cabal[@]}" list-bin exe:gen-auction-validator-blueprint)"
case "$(uname -s)" in
Darwin)
links="$(otool -L "$bin")"
if ! echo "$links" | grep -qF "$CRYPTO_MARKER"; then
fail "executable does not link crypto libs from the plinth cache ($CRYPTO_MARKER):
$links"
fi
for bad in /nix/store /opt/homebrew "/usr/local/lib"; do
if echo "$links" | grep -qE "(libsodium|libsecp256k1|libblst).*$bad|$bad.*(libsodium|libsecp256k1|libblst)"; then
fail "executable links crypto libs from $bad:
$links"
fi
done
note "otool: crypto libs come from the plinth cache only"
;;
Linux)
links="$(ldd "$bin" 2>/dev/null || true)"
if echo "$links" | grep -E 'libsodium|libsecp256k1|libblst' | grep -vqF "$CRYPTO_MARKER"; then
fail "executable resolves crypto libs outside the plinth cache ($CRYPTO_MARKER):
$links"
fi
note "ldd: crypto libs come from the plinth cache only (or are absent/static)"
;;
esac

echo "build-ghc-cabal: SUCCESS (ghc $GHC_VERSION)"
109 changes: 109 additions & 0 deletions .github/ci/build-nix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
#
# Build a Nix project end-to-end: create it with install.sh --env nix (or
# take a pre-created one as $1), enter its `nix develop` shell and run
# `cabal build all` plus the example blueprint.
#
# .github/ci/build-nix.sh [TREE] [--shell ghc96|ghc912|default]
#
# Demeter projects have identical nix content, so this also covers demeter.
#
# Requirements: nix with flakes. Configure IOG's binary caches (see the
# nix-setup-guide) or the first run will build GHC from source.
#
# Environment:
# PLINTH_CI_KEEP=1 Keep the scratch directory (printed) for inspection.

set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT"

TREE=""
SHELL_NAME="default"
while [ $# -gt 0 ]; do
case "$1" in
--shell) shift; SHELL_NAME="${1:?--shell needs an argument}" ;;
--shell=*) SHELL_NAME="${1#--shell=}" ;;
*) TREE="$1" ;;
esac
shift
done

fail() { echo "build-nix: FAIL: $*" >&2; exit 1; }
note() { echo "build-nix: $*"; }

if ! command -v nix >/dev/null 2>&1; then
fail "nix not on PATH"
fi

WORK="$(mktemp -d "${TMPDIR:-/tmp}/plinth-build-nix.XXXXXX")"
# nix's path: fetcher rejects paths with symlinked ancestors (macOS /var);
# use the physical path.
WORK="$(cd "$WORK" && pwd -P)"
cleanup() {
if [ "${PLINTH_CI_KEEP:-0}" = 1 ]; then
echo "build-nix: scratch dir kept: $WORK"
else
rm -rf "$WORK"
fi
}
trap cleanup EXIT

PROJECT="$WORK/project"
if [ -z "$TREE" ]; then
if ! sh "$ROOT/install.sh" --yes --env nix --from "$ROOT" --dir "$PROJECT" \
>"$WORK/install.log" 2>&1; then
cat "$WORK/install.log" >&2
fail "install.sh --env nix failed"
fi
else
mkdir -p "$PROJECT"
cp -R "$TREE/." "$PROJECT/"
fi
if [ ! -f "$PROJECT/flake.nix" ]; then
fail "$PROJECT is not a nix project"
fi

# cd matters: `nix develop --command` runs in the calling directory, and the
# in-shell cabal must build the branch copy, not whatever cwd we came from.
cd "$PROJECT"

note "entering nix develop path:$PROJECT#$SHELL_NAME (first run may take a while)..."
# shellcheck disable=SC2016 # the inner script must expand inside the shell
if ! nix develop "path:$PROJECT#$SHELL_NAME" --accept-flake-config --command bash -c '
set -euo pipefail
note() { echo "build-nix(shell): $*"; }

case "$(command -v ghc)" in
/nix/store/*) note "ghc $(ghc --numeric-version) from /nix/store" ;;
*) echo "build-nix(shell): FAIL: ghc not from /nix/store: $(command -v ghc)" >&2; exit 1 ;;
esac
case "$(command -v cabal)" in
/nix/store/*) note "cabal $(cabal --numeric-version) from /nix/store" ;;
*) echo "build-nix(shell): FAIL: cabal not from /nix/store: $(command -v cabal)" >&2; exit 1 ;;
esac
if ! pkg-config --exists libsodium libsecp256k1 libblst; then
echo "build-nix(shell): FAIL: crypto libs not visible to pkg-config" >&2
exit 1
fi
note "crypto libs provided by the shell: sodium $(pkg-config --modversion libsodium), secp256k1 $(pkg-config --modversion libsecp256k1), blst $(pkg-config --modversion libblst)"

if [ -n "${CI:-}" ]; then
note "CI: running cabal update"
cabal update
fi

note "building (cabal build all)..."
cabal build all
cabal run -v0 exe:gen-auction-validator-blueprint -- blueprint.json
if [ ! -s blueprint.json ]; then
echo "build-nix(shell): FAIL: empty blueprint" >&2
exit 1
fi
note "blueprint OK: $(wc -c < blueprint.json | tr -d " ") bytes"
'; then
fail "nix shell build failed"
fi

echo "build-nix: SUCCESS (shell: $SHELL_NAME)"
Loading
Loading