Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ jobs:
sudo apt-get update
sudo apt-get install -y shellcheck bats

- name: Verify phpvm.sh matches linux/src (drift check)
run: bash ./build.sh --check

- name: ShellCheck
# Deliberately only the generated file: linux/src/*.sh are fragments,
# not standalone scripts. The drift check above guarantees the generated
# file is what those modules say.
run: shellcheck linux/install.sh linux/phpvm.sh

- name: Bats (offline)
Expand Down
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,22 +395,28 @@ TS/NTS + toolchain and downloads matching extension DLLs.

## Development

`windows/phpvm.ps1` is **generated** — do not edit it directly. The Windows
sources live in `windows/src/*.ps1`, one file per domain, and are concatenated
back into the single shipped script:
`windows/phpvm.ps1` and `linux/phpvm.sh` are **generated** — do not edit them
directly. The sources live in `windows/src/*.ps1` and `linux/src/*.sh`, one file
per domain, and are concatenated back into the single shipped script per OS:

```powershell
pwsh ./build.ps1 # rebuild windows/phpvm.ps1 from windows/src/
pwsh ./build.ps1 -Check # fail if the two have drifted (what CI gates on)
```

The numeric filename prefixes set the concat order and are load-bearing:
`00-header.ps1` opens with `param()`, which PowerShell requires to be the first
statement, and `99-entry.ps1` closes with the command dispatch, which has to see
every function already defined. Distribution is unaffected — the installer and
`phpvm upgrade` still fetch one file.
```bash
bash ./build.sh # rebuild linux/phpvm.sh from linux/src/
bash ./build.sh --check # fail if the two have drifted (what CI gates on)
```

`linux/phpvm.sh` is hand-written and not part of the build.
The numeric filename prefixes set the concat order. On Windows it is
load-bearing throughout: `00-header.ps1` opens with `param()`, which PowerShell
requires to be the first statement, and `99-entry.ps1` closes with the command
dispatch, which has to see every function already defined. On Linux only the
ends matter — bash resolves function bodies at call time, so `00-header.sh`
(constants) must come first and `99-entry.sh` (source-time init) last; the
modules in between are ordered for readability. Distribution is unaffected — the
installer and `phpvm upgrade` still fetch one file per OS.

Tests:

Expand Down
79 changes: 79 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# ==============================================================================
# Concatenate linux/src/*.sh into the shipped linux/phpvm.sh.
#
# phpvm is distributed as a single file: linux/install.sh downloads
# linux/phpvm.sh straight from the repo and `phpvm upgrade` replaces it in
# place. Splitting the sources therefore has to collapse back into one file at
# build time rather than at load time.
#
# Modules concatenate in filename order, which is why they are numbered. In
# bash only two things about that order are load-bearing: 00-header.sh defines
# the constants everything else reads, and 99-entry.sh ends with the
# source-time init block, which calls functions that must already be defined.
# Function definition order in between is free — pick it for readability.
#
# Usage:
# bash ./build.sh # write linux/phpvm.sh
# bash ./build.sh --check # compare only; non-zero exit on drift (CI gate)
# ==============================================================================
set -euo pipefail

root="$(cd "$(dirname "$0")" && pwd)"
src_dir="$root/linux/src"
out_file="$root/linux/phpvm.sh"

check=0
case "${1:-}" in
--check) check=1 ;;
"") ;;
*) echo "usage: build.sh [--check]" >&2; exit 2 ;;
esac

# LC_ALL=C so the glob sorts the same everywhere; the repo stores LF only.
modules=$(LC_ALL=C ls "$src_dir"/*.sh 2>/dev/null || true)
[[ -n "$modules" ]] || { echo "No modules found in $src_dir" >&2; exit 1; }

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT

{
cat <<'BANNER'
#!/usr/bin/env bash
# ==============================================================================
# GENERATED FILE - DO NOT EDIT
# Built from linux/src/*.sh (concatenated in filename order).
# Edit a module there, then run: bash ./build.sh
# CI fails the drift check if this file and the modules disagree.
# ==============================================================================
BANNER

count=0
while IFS= read -r m; do
name=$(basename "$m")
dashes=$(printf '%*s' $(( 74 - ${#name} > 1 ? 74 - ${#name} : 1 )) '' | tr ' ' '-')
printf '\n# --- src/%s %s\n' "$name" "$dashes"
# $(cat) drops trailing newlines - each module contributes exactly one
# trailing blank line, no matter how it was saved.
printf '%s\n' "$(cat "$m")"
count=$(( count + 1 ))
done <<< "$modules"
} > "$tmp"

module_count=$(printf '%s\n' "$modules" | wc -l | tr -d ' ')

if (( check )); then
[[ -f "$out_file" ]] || { echo "Missing $out_file - run: bash ./build.sh" >&2; exit 1; }
if diff -u "$out_file" "$tmp" > /dev/null; then
echo "OK: linux/phpvm.sh matches linux/src/*.sh ($module_count modules)."
exit 0
fi
echo "DRIFT: linux/phpvm.sh does not match linux/src/*.sh."
echo "Rebuild with: bash ./build.sh"
diff -u "$out_file" "$tmp" | head -40
exit 1
fi

cp "$tmp" "$out_file"
chmod +x "$out_file"
echo "Built linux/phpvm.sh from $module_count modules ($(wc -l < "$out_file" | tr -d ' ') lines)."
2 changes: 1 addition & 1 deletion linux/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

set -e

PHPVM_VERSION="1.14.0"
PHPVM_VERSION="1.15.0"
PHPVM_DIR="${PHPVM_DIR:-$HOME/.phpvm}"
PHPVM_REPO="https://raw.githubusercontent.com/devhardiyanto/phpvm/main"

Expand Down
Loading
Loading