Ctrl K
ComunidadGuías…

script/bootstrap-deps

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# SPDX-FileCopyrightText: 2026 GPForum Contributors
# SPDX-License-Identifier: BSD-3-Clause

set -eu

# Install locked CPAN distributions in parallel through cpm under selected Perl.
# Carton maintains and checks cpanfile.snapshot; cpm may resolve only from it.
# This script does not install an interpreter; does not sideload application
# dists with cpanm; and does not delete local/.

root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
cd "$root"

perl_bin=$(script/gpforum-system-perl --require)
GPFORUM_PERL=$perl_bin
PATH="$(dirname "$perl_bin"):$PATH"
export PATH GPFORUM_PERL
printf 'bootstrap-deps: selected perl -> %s\n' "$perl_bin"

with_postgres=0
production=0
update=0
cached=0
rebuild_local=0
for_setup=0
dry_run=0

usage() {
    cat <<'EOF'
Usage: script/bootstrap-deps [--postgres] [--production] [--update] [--cached] [--rebuild-local]
                             [--for-setup] [--dry-run]

  --postgres       include the cpanfile postgres feature (DBD::Pg, Mojo::Pg,
                   Test::PostgreSQL). Requires pg_config / libpq.
  --production     leave out the develop phase (Perl::Critic, Perl::Tidy,
                   Devel::Cover, Devel::NYTProf and what they pull in): what a
                   production host runs, per ADR 0109.
  --update         refresh cpanfile.snapshot (development lock update). Do not
                   use this on production hosts.
  --cached         install from vendor/cache after `carton bundle`.
  --rebuild-local  rename an existing local/ to local.rebuild.<epoch> then
                   reinstall (does not delete). Use when local/ is incomplete
                   or built for another Perl.
  --for-setup      what gpforum setup runs on a checkout without its
                   dependencies: say so first, and end with a line of
                   setup's kind instead of the next step, setup going on
  --dry-run        with --for-setup, say what it would install, install
                   nothing, and exit 1

Default production/CI path: cpm install --resolver snapshot --no-default-resolvers
Parallel workers: GPFORUM_CPM_WORKERS=5 (lower it on memory-constrained hosts)
HTTPS mirror: PERL_CARTON_MIRROR=https://cpan.metacpan.org/
EOF
}

for argument in "$@"; do
    case "$argument" in
        --postgres)
            with_postgres=1
            ;;
        --production)
            production=1
            ;;
        --update|--dev)
            update=1
            ;;
        --cached)
            cached=1
            ;;
        --rebuild-local)
            rebuild_local=1
            ;;
        --for-setup)
            for_setup=1
            ;;
        --dry-run)
            dry_run=1
            ;;
        --help|-h)
            usage
            exit 0
            ;;
        *)
            echo "bootstrap-deps: unknown option: $argument" >&2
            usage >&2
            exit 1
            ;;
    esac
done

# A lock refresh without the develop phase would drop the maintainers' tools
# from cpanfile.snapshot for everyone.
if [ "$production" -eq 1 ] && [ "$update" -eq 1 ]; then
    echo "bootstrap-deps: --production cannot be combined with --update: the lock must keep the develop tools." >&2
    exit 2
fi

if [ ! -f cpanfile ]; then
    echo "bootstrap-deps: cpanfile is missing" >&2
    exit 1
fi

if [ ! -f cpanfile.snapshot ]; then
    if [ "$update" -eq 0 ]; then
        echo "bootstrap-deps: cpanfile.snapshot is required for a locked install." >&2
        echo "Refresh the lock on a development host with: script/bootstrap-deps --update" >&2
        exit 1
    fi
    echo "bootstrap-deps: no snapshot yet; carton install will create cpanfile.snapshot" >&2
fi

if [ "$with_postgres" -eq 1 ] && [ ! -f cpanfile.postgres ]; then
    echo "bootstrap-deps: cpanfile.postgres is missing" >&2
    exit 1
fi

# shellcheck disable=SC2016 # Config belongs to Perl.
current_perl=$("$perl_bin" -MConfig -e 'print $Config{version}')
if [ "$rebuild_local" -eq 1 ] && [ -e local ]; then
    stamp=$(date +%s)
    backup="local.rebuild.${stamp}"
    echo "bootstrap-deps: --rebuild-local moving local/ -> ${backup}"
    mv local "$backup"
fi
PERL5LIB= PERL5OPT= "$perl_bin" -I"$root/lib" -MGPForum::Infrastructure::PerlRuntime \
    -e 'GPForum::Infrastructure::PerlRuntime->check(shift)' "$root"

find_pg_config() {
    if [ -n "${GPFORUM_PG_CONFIG:-}" ] && [ -x "${GPFORUM_PG_CONFIG}" ]; then
        printf '%s\n' "${GPFORUM_PG_CONFIG}"
        return 0
    fi
    if command -v pg_config >/dev/null 2>&1; then
        command -v pg_config
        return 0
    fi
    for candidate in \
        /opt/homebrew/opt/postgresql@18/bin/pg_config \
        /opt/homebrew/opt/postgresql@17/bin/pg_config \
        /opt/homebrew/opt/postgresql@16/bin/pg_config \
        /usr/local/opt/postgresql@18/bin/pg_config \
        /opt/homebrew/opt/libpq/bin/pg_config \
        /usr/local/opt/libpq/bin/pg_config \
        /Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
    do
        if [ -x "$candidate" ]; then
            printf '%s\n' "$candidate"
            return 0
        fi
    done
    return 1
}

if [ "$with_postgres" -eq 1 ]; then
    pg_config_bin=$(find_pg_config) || {
        echo "bootstrap-deps: pg_config is required for DBD::Pg." >&2
        echo "Run script/system-preflight for OS package guidance." >&2
        exit 1
    }
    PATH="$(dirname "$pg_config_bin"):$PATH"
    export PATH
    PG_CONFIG="$pg_config_bin"
    export PG_CONFIG
    printf 'bootstrap-deps: using pg_config -> %s\n' "$pg_config_bin"
fi

# Net::SSLeay, under IO::Socket::SSL, builds against OpenSSL's headers.
# Debian and Ubuntu install them under /usr with libssl-dev (libpq-dev does
# not bring it), FreeBSD has them in base; macOS has them only in Homebrew's
# keg-only openssl@3, which Net::SSLeay finds when OPENSSL_PREFIX names it.
if [ -z "${OPENSSL_PREFIX:-}" ] && [ ! -f /usr/include/openssl/ssl.h ]; then
    for candidate in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do
        if [ -f "$candidate/include/openssl/ssl.h" ]; then
            OPENSSL_PREFIX=$candidate
            export OPENSSL_PREFIX
            printf 'bootstrap-deps: using OpenSSL -> %s\n' "$candidate"
            break
        fi
    done
fi

# EV's header probe checks Perl's configured include directories, which may
# omit the active macOS SDK. Ask its supported build switches for the native
# backend required by GPForum::OS::Reactor instead of silently compiling only
# select. The compiler still checks the platform headers normally.
case $(uname -s) in
    Darwin) EV_KQUEUE=1; export EV_KQUEUE ;;
    # FreeBSD's optional libinotify headers do not provide Linux statfs.
    # EV's supported switch prevents that false probe; kqueue stays mandatory.
    FreeBSD) EV_KQUEUE=1; EV_INOTIFY=0; export EV_KQUEUE EV_INOTIFY ;;
    Linux) EV_EPOLL=1; export EV_EPOLL ;;
esac

if [ -z "${PERL_CARTON_MIRROR:-}" ]; then
    PERL_CARTON_MIRROR=https://cpan.metacpan.org/
fi
export PERL_CARTON_MIRROR

case "$PERL_CARTON_MIRROR" in
    https://*|file://*|/*)
        ;;
    *)
        echo "bootstrap-deps: PERL_CARTON_MIRROR must be HTTPS or a local path (got $PERL_CARTON_MIRROR)" >&2
        exit 1
        ;;
esac

if [ "$update" -eq 1 ]; then
    installer=carton
    set -- install
    if [ "$cached" -eq 1 ]; then
        set -- "$@" --cached
    fi
    if [ "$with_postgres" -eq 0 ]; then
        set -- "$@" --without postgres
    fi
else
    installer=cpm
    workers=${GPFORUM_CPM_WORKERS:-5}
    case "$workers" in
        ''|*[!0-9]*|0*)
            echo "bootstrap-deps: GPFORUM_CPM_WORKERS must be a positive integer" >&2
            exit 2
            ;;
    esac
    mirror=$PERL_CARTON_MIRROR
    if [ "$cached" -eq 1 ]; then
        mirror="$root/vendor/cache"
        if [ ! -d "$mirror/authors/id" ]; then
            echo "bootstrap-deps: --cached requires vendor/cache from carton bundle" >&2
            exit 1
        fi
    fi
    phases=configure,build,test,runtime,develop
    if [ "$production" -eq 1 ]; then
        phases=configure,build,test,runtime
    fi
    # Reading a snapshot alone still allows cpm's default CPAN resolvers.
    # Disable them so missing or incompatible pins fail closed. Keep the
    # existing Carton policy: skip upstream tests, retain our test phase.
    set -- install --cpanfile cpanfile --snapshot cpanfile.snapshot \
        --resolver snapshot --no-default-resolvers --mirror "$mirror" \
        --local-lib-contained local --home local --workers "$workers" --no-test \
        --final-install all \
        --top-level-phase "$phases"
    if [ "$with_postgres" -eq 1 ]; then
        set -- "$@" --feature postgres
    fi
fi

snapshot_count() {
    if [ ! -f cpanfile.snapshot ]; then
        echo 0
        return
    fi
    grep -c '^  [A-Za-z0-9_.-]*$' cpanfile.snapshot || echo 0
}

# gpforum setup on a checkout without its dependencies runs this first
# (GPForum::CLI::FrontDoor::Carton): it says what it installs, as make
# install-deps-production would, in the operator's language.
if [ "$for_setup" -eq 1 ] && [ -r "$root/script/lib/words.sh" ]; then
    # shellcheck source=script/lib/words.sh
    . "$root/script/lib/words.sh"
    if [ "$production" -eq 1 ]; then
        make_target=install-deps-production
    else
        make_target=install-deps-postgres
    fi
    words_say system.deps_for_setup 'gpforum setup installs the dependencies into {local} first, as make {target} does: {installer} {command}' \
        "local=$root/local" "target=$make_target" "installer=$installer" "command=$*"
    if [ "$dry_run" -eq 1 ]; then
        words_say system.deps_dry_run 'Nothing was installed: --dry-run says what setup would do.'
        exit 1
    fi
fi

before_count=$(snapshot_count)
if [ "$update" -eq 1 ] && [ -f cpanfile.snapshot ]; then
    cp cpanfile.snapshot "${TMPDIR:-/tmp}/gpforum-cpanfile.snapshot.bak.$$"
fi

echo "bootstrap-deps: PERL_CARTON_MIRROR=$PERL_CARTON_MIRROR"
echo "bootstrap-deps: running $installer $*"
"script/gpforum-$installer" "$@"

after_count=$(snapshot_count)
if [ "$update" -eq 1 ] && [ "$before_count" -gt 20 ] && [ "$after_count" -lt "$before_count" ]; then
    echo "bootstrap-deps: carton install shrank cpanfile.snapshot from $before_count to $after_count distributions." >&2
    echo "Restoring the previous lock. Review local/ Perl compatibility and retry." >&2
    cp "${TMPDIR:-/tmp}/gpforum-cpanfile.snapshot.bak.$$" cpanfile.snapshot
    rm -f "${TMPDIR:-/tmp}/gpforum-cpanfile.snapshot.bak.$$"
    exit 1
fi
rm -f "${TMPDIR:-/tmp}/gpforum-cpanfile.snapshot.bak.$$"

echo "bootstrap-deps: verifying lock with carton check"
script/gpforum-carton check

# Record only after installation and lock verification succeeded. The
# manifest follows the target architecture and contains no prefix binding.
mkdir -p "$root/local"
PERL5LIB= PERL5OPT= "$perl_bin" -I"$root/lib" -MGPForum::Infrastructure::PerlRuntime \
    -e 'GPForum::Infrastructure::PerlRuntime->record_installation(shift)' "$root"

# The interpreter shim script/gpforum-carton puts first on PATH lives in
# local/, and is made the first time a command runs. Made here, by whoever
# installs: the deployment guide keeps the code tree root's, and the service
# user, who cannot write local/, printed "mkdir: ... Permission denied" on
# every start and every command.
script/gpforum-carton exec perl -e 1

# Whether this host is set up already: the environment file gpforum reads is
# in place, so this install is a step of an upgrade, and gpforum upgrade
# printed the ones after it.
# shellcheck disable=SC2016 # Perl, not the shell, reads the variables
set_up=$(script/gpforum-carton exec perl -I"$root/lib" -MGPForum::OS -e \
    'my $file = $ENV{GPFORUM_ENV_FILE} // GPForum::OS->detect->environment_file; print -e $file ? 1 : 0' \
    2>/dev/null) || set_up=0
[ -n "$set_up" ] || set_up=0

# What to type next, in the operator's language: a host not set up yet is
# set up, a production one as root, by the path that works before the link,
# and a checkout for development, as the README goes on. An upgrade has its
# next step printed already, and a lock refresh is a maintainer's: nothing
# after either. It named script/system-preflight, a step the quick start
# no longer has, even in an upgrade.
if [ "$update" -eq 0 ] && [ -r "$root/script/lib/words.sh" ]; then
    # shellcheck source=script/lib/words.sh
    . "$root/script/lib/words.sh"
    if [ "${for_setup:-0}" -eq 1 ]; then
        echo
        words_mark ok "$(words_say system.deps_installed 'dependencies: installed into {local}, for Perl {version}' \
            "local=$root/local" "version=$current_perl")"
    elif [ "$set_up" -eq 1 ]; then
        :
    elif [ "$production" -eq 1 ]; then
        echo
        words_say system.next_setup 'Next: set this host up, with sudo {gpforum} setup' \
            "gpforum=$root/bin/gpforum"
    else
        # As the README's quick start types it: through sudo, which writes
        # /etc/gpforum and links /usr/local/bin, but on macOS, whose are
        # Homebrew's and the operator's own.
        checkout_setup='bin/gpforum setup --environment development'
        [ "$(uname -s)" = Darwin ] || checkout_setup="sudo $checkout_setup"
        echo
        words_say system.next_setup_checkout 'Next: set this checkout up, with {command}' \
            "command=$checkout_setup"
    fi
fi