-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdev
More file actions
executable file
·148 lines (133 loc) · 4.58 KB
/
Copy pathdev
File metadata and controls
executable file
·148 lines (133 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env bash
#
# dev — friction-free PHP / Composer / PHPUnit for this project, via Podman.
#
# Nothing PHP-related is installed on your host. Pick a PHP version and run
# commands against your live working tree. Changes to composer.json and
# composer.lock are written back to disk so you can commit them.
#
# USAGE
# ./dev <php-version> [command...] Run a command (default: a shell)
# ./dev build <php-version> (Re)build the image for a version
# ./dev --help Show this help
#
# THE UPGRADE LOOP (the thing you actually do here)
# 1. Branch off, then edit composer.json to widen the constraints, e.g.
# "php": "^8.2",
# "illuminate/contracts": "^10.0",
# "illuminate/support": "^10.0"
# 2. Re-resolve dependencies for the target PHP (rewrites composer.lock):
# ./dev 8.2 composer update
# 3. If that conflicts, find out why, then iterate:
# ./dev 8.2 composer why-not laravel/framework 10.0.0
# ./dev 8.2 composer require illuminate/support:"^10.0" --no-update
# ./dev 8.2 composer update
# 4. Run the tests:
# ./dev 8.2 test # alias for: composer test
# 5. Commit composer.json + composer.lock.
#
# Try several PHP versions just as easily (each keeps its own vendor/ cache):
# ./dev 8.1 test
# ./dev 8.2 test
# ./dev 8.3 test
#
# ALIASES
# test -> composer test (runs phpunit)
# lint -> composer lint (runs phpcs)
# install -> composer install
# update -> composer update
#
# Anything else is passed straight through, e.g.:
# ./dev 8.3 php -v
# ./dev 8.3 composer outdated
# ./dev 8.3 vendor/bin/phpunit --filter ApplyWhen
#
# NOTES
# * First run on a given PHP version builds the image automatically.
# * Dependencies live in a per-version Podman volume (apposite-vendor-php<x>);
# Composer's download cache is shared across versions (apposite-composer-cache).
# * `./dev <ver> test` will run `composer install` first if vendor/ is missing,
# so you can't accidentally run tests without dependencies.
#
set -euo pipefail
IMAGE_PREFIX="localhost/apposite"
CONTEXT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
sed -n '3,/^set -euo pipefail$/p' "$0" | sed 's/^# \{0,1\}//'
exit 0
}
die() { echo "dev: $*" >&2; exit 1; }
image_for() { echo "${IMAGE_PREFIX}:php$1"; }
vendor_vol() { echo "apposite-vendor-php$1"; }
cache_vol() { echo "apposite-composer-cache"; }
build_image() {
local ver="$1"
local tag; tag="$(image_for "$ver")"
echo "dev: building ${tag} (PHP ${ver})" >&2
podman build --build-arg "PHP_VERSION=${ver}" -t "${tag}" "${CONTEXT}"
}
# Returns 0 if the command needs vendor/ to be present (and so should trigger an
# automatic `composer install` when it is missing).
needs_deps() {
local first="${1:-}" second="${2:-}"
case "$first" in
test|lint|phpunit) return 0 ;;
composer) case "$second" in test|lint) return 0 ;; esac ;;
esac
return 1
}
run() {
local ver="$1"; shift
[[ -n "$ver" ]] || die "missing PHP version (try: ./dev --help)"
local tag; tag="$(image_for "$ver")"
if ! podman image exists "${tag}" 2>/dev/null; then
build_image "$ver"
fi
# Build the command array, expanding aliases.
local cmd=()
if [[ $# -gt 0 ]]; then
case "$1" in
test|lint|install|update|require|remove) cmd=(composer "$@") ;;
*) cmd=("$@") ;;
esac
else
cmd=(bash)
fi
local env=(-e "DEV_PHP=${ver}")
if needs_deps "${cmd[@]:-}"; then env+=(-e DEV_ENSURE_DEPS=1); fi
local tty=()
[[ -t 0 ]] && tty=(--interactive --tty)
# :z relabels the bind-mounted tree to container_file_t so SELinux lets the
# confined container read your source. It's harmless to host access (your
# login session is unconfined) and self-healing on each run.
podman run --rm "${tty[@]}" "${env[@]}" \
-v "${CONTEXT}":/app:z \
-v "$(vendor_vol "${ver}")":/app/vendor \
-v "$(cache_vol)":/tmp/composer \
-w /app \
"${tag}" \
sh -lc '
if [ "${DEV_ENSURE_DEPS:-}" = "1" ] && [ ! -f vendor/autoload.php ]; then
echo "dev: vendor/ not present - running composer install..." >&2
composer install || {
echo "dev: composer install failed." >&2
echo "dev: if composer.lock targets a different PHP, try: ./dev ${DEV_PHP} update" >&2
exit 1
}
fi
exec "$@"
' -- "${cmd[@]}"
}
main() {
case "${1:-}" in
-h|--help|help|"") usage ;;
build)
[[ $# -ge 2 ]] || die "usage: ./dev build <php-version>"
build_image "${2:?missing PHP version}"
;;
*)
run "$@"
;;
esac
}
main "$@"