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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ Install and switch PHP versions on **Windows** (CMD + PowerShell) and **Linux**
### Windows

```powershell
# Download both files (phpvm.ps1 + install.ps1) to the same folder, then:
.\install.ps1
irm https://raw.githubusercontent.com/devhardiyanto/phpvm/main/windows/install.ps1 | iex
```

Restart your terminal. No admin required.

Or, if you would rather read the script before running it:

```powershell
# Download both files (phpvm.ps1 + install.ps1) to the same folder, then:
.\install.ps1
```

### Linux

```bash
Expand Down
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.9.0"
PHPVM_VERSION="1.9.1"
PHPVM_DIR="${PHPVM_DIR:-$HOME/.phpvm}"
PHPVM_REPO="https://raw.githubusercontent.com/devhardiyanto/phpvm/main"

Expand Down
84 changes: 59 additions & 25 deletions linux/phpvm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# phpvm use 8.3.0
# ==============================================================================

PHPVM_VERSION="1.9.0"
PHPVM_VERSION="1.9.1"
PHPVM_DIR="${PHPVM_DIR:-$HOME/.phpvm}"
PHPVM_VERSIONS="$PHPVM_DIR/versions"
PHPVM_CURRENT="$PHPVM_DIR/current"
Expand Down Expand Up @@ -312,11 +312,17 @@ _phpvm_run_logged() {
return $?
fi

# phpvm.sh is sourced into an interactive shell, where job control is on and
# would print "[3] 2015" / "[3] + done ..." around our background build.
# Turn the monitor off for the duration and put it back exactly as we found it.
local had_monitor=0
case "$-" in *m*) had_monitor=1; set +m ;; esac

"$@" >> "$PHPVM_LOG" 2>&1 &
local pid=$!

# Ctrl+C must take the build down with us, not orphan it.
trap 'kill "$pid" 2>/dev/null; printf "\r\033[K" >&2; trap - INT; return 130' INT
trap 'kill "$pid" 2>/dev/null; printf "\r\033[K" >&2; [[ $had_monitor -eq 1 ]] && set -m; trap - INT; return 130' INT

# Plain ASCII frames + explicit index: ${var:i++:1} is a bashism that bites
# in zsh, and Unicode braille spinners mangle on older terminals.
Expand All @@ -334,6 +340,7 @@ _phpvm_run_logged() {
wait "$pid"
local rc=$?
trap - INT
[[ $had_monitor -eq 1 ]] && set -m
printf '\r\033[K' >&2

elapsed=$((SECONDS - start))
Expand Down Expand Up @@ -493,30 +500,26 @@ phpvm_install() {
"--with-pear"
)

cd "$src_dir" || {
_err "Could not enter source directory: $src_dir"
rm -rf "$target"
return 1
}
./buildconf --force &>>"$PHPVM_LOG" 2>&1 || true # needed only for git checkouts
_phpvm_run_logged "Configuring" ./configure "${configure_opts[@]}" || {
_err "Configure failed. See log: $PHPVM_LOG"
rm -rf "$target"
return 1
}

# Build
local cpus
cpus=$(_phpvm_cpus)
_phpvm_run_logged "Building with $cpus cores" make -j"$cpus" || {
_err "Build failed. See log: $PHPVM_LOG"
rm -rf "$target"
return 1
}

# Install
_phpvm_run_logged "Installing" make install || {
_err "Install failed. See log: $PHPVM_LOG"
# The whole build runs in a subshell so the `cd` cannot escape: phpvm.sh is
# sourced, so a bare cd here would strand the user's own shell in the build
# directory — which is then deleted, leaving them in a dangling cwd.
(
cd "$src_dir" || { _err "Could not enter source directory: $src_dir"; exit 1; }

./buildconf --force &>>"$PHPVM_LOG" 2>&1 || true # needed only for git checkouts

_phpvm_run_logged "Configuring" ./configure "${configure_opts[@]}" \
|| { _err "Configure failed. See log: $PHPVM_LOG"; exit 1; }

_phpvm_run_logged "Building with $cpus cores" make -j"$cpus" \
|| { _err "Build failed. See log: $PHPVM_LOG"; exit 1; }

_phpvm_run_logged "Installing" make install \
|| { _err "Install failed. See log: $PHPVM_LOG"; exit 1; }
) || {
rm -rf "$target"
return 1
}
Expand All @@ -538,9 +541,40 @@ phpvm_install() {
# Activate the freshly built version right away, unless opted out.
if [[ $no_use -eq 1 ]]; then
_dim "Not switching (--no-use). Run: phpvm use $ver"
return 0
else
phpvm_use "$ver"
fi
phpvm_use "$ver"

_phpvm_older_patch_hint "$ver"
}

# `phpvm install 8` resolves to the newest patch and installs it alongside any
# older patch of the same line. Point that out rather than removing it: another
# project may still pin the old patch in .phpvmrc.
_phpvm_older_patches() {
local ver="$1"
[[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 0
[[ -d "$PHPVM_VERSIONS" ]] || return 0

local line="${ver%.*}" name
find "$PHPVM_VERSIONS" -mindepth 1 -maxdepth 1 -type d -name "$line.*" 2>/dev/null \
| while read -r d; do
name=$(basename "$d")
[[ "$name" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || continue
[[ "$name" == "$ver" ]] && continue
# Keep only patches strictly below $ver.
[[ "$(printf '%s\n%s' "$name" "$ver" | sort -V | head -1)" == "$name" ]] && echo "$name"
done | sort -V
}

_phpvm_older_patch_hint() {
local ver="$1" older newest
older=$(_phpvm_older_patches "$ver")
[[ -z "$older" ]] && return 0

newest=$(echo "$older" | tail -1)
_dim "Older patch of ${ver%.*} still installed: $(echo "$older" | paste -sd ', ')"
_dim "Remove it with: phpvm uninstall $newest"
}

# ==============================================================================
Expand Down
39 changes: 39 additions & 0 deletions tests/linux/commands.bats
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,42 @@ EOF
[ "$status" -ne 0 ]
[[ "$output" == *"Usage: phpvm install"* ]]
}

# ---------- older-patch hint ----------

@test "older_patches: lists only lower patches of the same minor line" {
mkdir -p "$PHPVM_VERSIONS"/{8.5.6,8.5.8,8.5.10,8.3.31,7.4.33}
run _phpvm_older_patches 8.5.8
[ "$status" -eq 0 ]
[ "$output" = "8.5.6" ]
}

@test "older_patches: sorts numerically, not lexically" {
mkdir -p "$PHPVM_VERSIONS"/{8.5.2,8.5.10,8.5.11}
run _phpvm_older_patches 8.5.11
[ "$status" -eq 0 ]
[ "$output" = "8.5.2
8.5.10" ]
}

@test "older_patches: does not treat 8.50.x as part of the 8.5 line" {
mkdir -p "$PHPVM_VERSIONS"/{8.50.1,8.5.9}
run _phpvm_older_patches 8.5.9
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "older_patches: silent when it is the only patch of its line" {
mkdir -p "$PHPVM_VERSIONS"/{8.5.8,8.3.31}
run _phpvm_older_patches 8.5.8
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "older_patch_hint: names the version to uninstall" {
mkdir -p "$PHPVM_VERSIONS"/{8.5.6,8.5.8}
run _phpvm_older_patch_hint 8.5.8
[ "$status" -eq 0 ]
[[ "$output" == *"Older patch of 8.5 still installed: 8.5.6"* ]]
[[ "$output" == *"phpvm uninstall 8.5.6"* ]]
}
31 changes: 31 additions & 0 deletions tests/windows/Install.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,34 @@ Describe 'Invoke-Install --no-use parsing' {
Should -Invoke Resolve-PHPURL -Times 0
}
}

Describe 'Get-OlderPatch' {
BeforeAll {
. $PSScriptRoot/Common.ps1

# Build a fake $VERSIONS_DIR. 8.50.1 is here on purpose: it must not be
# mistaken for part of the 8.5 line.
$fakeVersions = Join-Path $TestDrive 'versions'
foreach ($v in @('7.4.33', '8.3.31', '8.5.2', '8.5.6', '8.5.8', '8.5.10', '8.5.11', '8.50.1')) {
New-Item -ItemType Directory -Path (Join-Path $fakeVersions $v) -Force | Out-Null
}
# Reassign $VERSIONS_DIR; functions read it dynamically.
$VERSIONS_DIR = $fakeVersions
}

It 'Lists only lower patches of the same minor line' {
Get-OlderPatch '8.5.8' | Should -Be @('8.5.2', '8.5.6')
}

It 'Sorts numerically, not lexically' {
Get-OlderPatch '8.5.11' | Should -Be @('8.5.2', '8.5.6', '8.5.8', '8.5.10')
}

It 'Does not treat 8.50.x as part of the 8.5 line' {
Get-OlderPatch '8.50.1' | Should -BeNullOrEmpty
}

It 'Returns nothing when it is the only patch of its line' {
Get-OlderPatch '8.3.31' | Should -BeNullOrEmpty
}
}
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.0
1.9.1
33 changes: 24 additions & 9 deletions windows/install.ps1
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# ==============================================================================
# install.ps1 - phpvm installer for Windows
# Run as normal user (no admin required)
# Usage: .\install.ps1
# Usage: irm https://raw.githubusercontent.com/devhardiyanto/phpvm/main/windows/install.ps1 | iex
# or: .\install.ps1 (from a clone, with phpvm.ps1 alongside)
# ==============================================================================

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$PHPVM_VERSION = "1.9.0"
$PHPVM_VERSION = "1.9.1"
$PHPVM_DIR = if ($env:PHPVM_DIR) { $env:PHPVM_DIR } else { "$env:USERPROFILE\.phpvm" }
$PHPVM_BIN = "$PHPVM_DIR\bin"

Expand Down Expand Up @@ -46,15 +47,29 @@ foreach ($d in @($PHPVM_DIR, "$PHPVM_DIR\versions", $PHPVM_BIN)) {
if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
}

# 2. Copy phpvm.ps1
$scriptSrc = Join-Path $PSScriptRoot "phpvm.ps1"
if (-not (Test-Path $scriptSrc)) {
Write-Host " [error] phpvm.ps1 not found next to install.ps1" -ForegroundColor Red
exit 1
# 2. Obtain phpvm.ps1 - from the clone if we are running off disk, otherwise
# from the repo, so `irm .../install.ps1 | iex` works with nothing local.
# ($PSScriptRoot is empty when this script is piped into iex.)
$PHPVM_REPO = "https://raw.githubusercontent.com/devhardiyanto/phpvm/main"
$scriptSrc = if ($PSScriptRoot) { Join-Path $PSScriptRoot "phpvm.ps1" } else { $null }

if ($scriptSrc -and (Test-Path $scriptSrc)) {
Copy-Item $scriptSrc "$PHPVM_DIR\phpvm.ps1" -Force
Write-Ok "Copied phpvm.ps1 -> $PHPVM_DIR\phpvm.ps1"
} else {
Write-Step "Downloading phpvm.ps1 ..."
try {
Invoke-WebRequest -Uri "$PHPVM_REPO/windows/phpvm.ps1" `
-OutFile "$PHPVM_DIR\phpvm.ps1" -UseBasicParsing
} catch {
# throw, not exit: under `irm ... | iex` an `exit` would close the user's
# whole session instead of just aborting the install.
Write-Host " [error] Could not download phpvm.ps1: $_" -ForegroundColor Red
throw "phpvm install aborted."
}
Write-Ok "Downloaded phpvm.ps1 -> $PHPVM_DIR\phpvm.ps1"
}
Copy-Item $scriptSrc "$PHPVM_DIR\phpvm.ps1" -Force
Unblock-File "$PHPVM_DIR\phpvm.ps1"
Write-Ok "Copied phpvm.ps1 -> $PHPVM_DIR\phpvm.ps1"

# 3. Create CMD launcher (phpvm.cmd) - works in CMD and PowerShell
$cmdLauncher = '@echo off
Expand Down
39 changes: 35 additions & 4 deletions windows/phpvm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

# -- Constants -----------------------------------------------------------------
$PHPVM_VERSION = "1.9.0"
$PHPVM_VERSION = "1.9.1"
$PHPVM_DIR = if ($env:PHPVM_DIR) { $env:PHPVM_DIR } else { "$env:USERPROFILE\.phpvm" }
$VERSIONS_DIR = "$PHPVM_DIR\versions"
$CURRENT_LINK = "$PHPVM_DIR\current"
Expand Down Expand Up @@ -490,9 +490,38 @@ function Invoke-Install ([string]$ver, [string]$flag) {
# Activate the freshly installed version right away, unless opted out.
if ($noUse) {
Write-Dim "Not switching (--no-use). Run: phpvm use $ver"
return
} else {
Invoke-Use $ver
}
Invoke-Use $ver

Show-OlderPatchHint $ver
}

# `phpvm install 8` resolves to the newest patch and installs it alongside any
# older patch of the same line. Point that out rather than removing it: another
# project may still pin the old patch in .phpvmrc.
function Get-OlderPatch ([string]$ver) {
if ($ver -notmatch '^\d+\.\d+\.\d+$') { return @() }
if (-not (Test-Path $VERSIONS_DIR)) { return @() }

$parts = $ver -split '\.'
$line = "$($parts[0]).$($parts[1])"

return @(
Get-ChildItem $VERSIONS_DIR -Directory -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name |
Where-Object { $_ -match '^\d+\.\d+\.\d+$' -and $_ -like "$line.*" } |
Where-Object { [version]$_ -lt [version]$ver } |
Sort-Object { [version]$_ }
)
}

function Show-OlderPatchHint ([string]$ver) {
$older = Get-OlderPatch $ver
if ($older.Count -eq 0) { return }

Write-Dim "Older patch of $(($ver -split '\.')[0..1] -join '.') still installed: $($older -join ', ')"
Write-Dim "Remove it with: phpvm uninstall $($older[-1])"
}

function Invoke-Use ([string]$ver) {
Expand Down Expand Up @@ -1302,7 +1331,9 @@ php "$composerPhar" %*
Write-Ok " phar : $composerPhar"
Write-Ok " shim : $composerBat"
Write-Host ""
& $info.Exe $composerPhar --version | ForEach-Object { Write-Host " $_" }
# 2>$null: composer writes its PHP-version banner and the "run diagnose" hint
# to stderr, which would bypass this pipeline and print unindented.
& $info.Exe $composerPhar --version 2>$null | ForEach-Object { Write-Host " $_" }
Write-Host ""
Write-Dim "Composer follows your active PHP version - no need to re-run after 'phpvm use'."
}
Expand Down