script/bootstrap-deps
Rama main · réplica de lectura del repositorio original.
#!/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