Skip to content

Commit cd81d63

Browse files
authored
fix: resolve DNF5 module stream naming and invalid recovery instructions (closes #20)
fix: resolve DNF5 module stream naming and invalid recovery instructions
2 parents b74fdca + db99084 commit cd81d63

3 files changed

Lines changed: 255 additions & 21 deletions

File tree

β€ŽCHANGELOG.mdβ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## [Unreleased]
44

5+
## [v1.12.2](https://github.com/Thavarshan/phpvm/compare/v1.12.1...v1.12.2) - 2026-06-27
6+
7+
### Fixed
8+
9+
- **DNF5 / Fedora 41+ module stream naming (issue #20):** `phpvm install` and `phpvm use` now resolve the actual php module stream name from `dnf module list php` instead of hardcoding `php:X.Y`. Remi streams (`remi-8.2`, `remi-8.3`, …) and RHEL AppStream streams (`8.2`) are both detected automatically.
10+
- **Destructive unconditional `dnf module reset` (issue #20):** The reset is now skipped when the correct stream is already enabled, preventing phpvm from clobbering a stream the user configured manually.
11+
- **Invalid DNF5 recovery instructions (issue #20):** `suggest_repository_setup` now emits `dnf config-manager setopt <repo>.enabled=1` on DNF5 (Fedora 41+) instead of the removed `--set-enabled` flag. Fedora instructions no longer reference the non-existent `remi-php82` repo; instead they direct users to enable the module stream (`dnf module enable php:remi-X.Y -y`).
12+
- **False "not found" for un-enabled Remi streams (issue #20):** `pkg_search_php` now uses stream resolution to detect `remi-X.Y` streams even before they are enabled, so phpvm correctly reports a version as installable rather than emitting the misleading "other PHP versions are available" message.
13+
514
## [v1.12.1](https://github.com/Thavarshan/phpvm/compare/v1.12.0...v1.12.1) - 2026-05-24
615

716
### Added

β€Žphpvm.shβ€Ž

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# shellcheck disable=SC2155 # Allow declare and assign on same line for better readability
1313

14-
PHPVM_VERSION="1.12.1"
14+
PHPVM_VERSION="1.12.2"
1515

1616
# Test mode flag
1717
PHPVM_TEST_MODE="${PHPVM_TEST_MODE:-false}"
@@ -931,11 +931,11 @@ pkg_search_php() {
931931
fi
932932
;;
933933
dnf)
934-
# Check for module stream availability (preferred for versioned PHP)
935-
if dnf module list php 2> /dev/null | command grep -Eq "php[[:space:]]+${version}[[:space:]]|${version}[[:space:]]+\["; then
934+
# Use stream resolution to handle both Remi (remi-X.Y) and AppStream (X.Y) naming
935+
if [ -n "$(dnf_resolve_php_stream "$version")" ]; then
936936
return 0
937937
fi
938-
# If any php module exists, signal "some versions exist"
938+
# If any php module exists, signal "some versions exist but not this one"
939939
if dnf module list php 2> /dev/null | command grep -q "^php"; then
940940
return 2
941941
fi
@@ -1204,6 +1204,39 @@ check_remi_repository() {
12041204
fi
12051205
}
12061206

1207+
# Return the DNF major version (4 or 5), or 0 if dnf not found
1208+
dnf_major_version() {
1209+
command dnf --version 2> /dev/null | command awk 'NR==1{split($1,a,"."); print a[1]+0}'
1210+
}
1211+
1212+
# Resolve the actual dnf module stream name for a PHP X.Y version.
1213+
# Remi uses "remi-X.Y", RHEL AppStream uses "X.Y".
1214+
# Prints the stream name (e.g. "remi-8.2") or nothing if not found.
1215+
dnf_resolve_php_stream() {
1216+
local version="$1"
1217+
command dnf module list php 2> /dev/null | command awk -v ver="$version" '
1218+
$1 == "php" {
1219+
stream = $2
1220+
# Exact match (AppStream: "8.2") or suffix match (Remi: "remi-8.2")
1221+
if (stream == ver || substr(stream, length(stream) - length(ver)) == "-" ver) {
1222+
# Prefer remi- streams when available; collect all and pick last-or-remi
1223+
if (substr(stream, 1, 5) == "remi-") { remi = stream }
1224+
else { plain = stream }
1225+
}
1226+
}
1227+
END { if (remi != "") print remi; else if (plain != "") print plain }
1228+
'
1229+
}
1230+
1231+
# Return 0 if the given php module stream is currently enabled, 1 otherwise.
1232+
dnf_stream_enabled() {
1233+
local stream="$1"
1234+
command dnf module list php 2> /dev/null | command awk -v s="$stream" '
1235+
$1 == "php" && $2 == s { if (index($3, "[e]") || index($4, "[e]") || index($0, "[e]")) found=1 }
1236+
END { exit (found ? 0 : 1) }
1237+
'
1238+
}
1239+
12071240
# Detect if PHP packages are available in current repositories
12081241
detect_php_availability() {
12091242
local version="$1"
@@ -1218,9 +1251,23 @@ suggest_repository_setup() {
12181251
local version="$1"
12191252
local major_minor
12201253
local major_version
1254+
local dnf_ver
1255+
1256+
# Emit the correct "dnf config-manager" syntax for the installed dnf version.
1257+
# DNF5 (Fedora 41+) removed --set-enabled; use "setopt <repo>.enabled=1" instead.
1258+
_dnf_enable_repo_cmd() {
1259+
local repo="$1"
1260+
dnf_ver=$(dnf_major_version)
1261+
if [ "${dnf_ver:-0}" -ge 5 ]; then
1262+
printf ' sudo dnf config-manager setopt %s.enabled=1\n' "$repo"
1263+
else
1264+
printf ' sudo dnf config-manager --set-enabled %s\n' "$repo"
1265+
fi
1266+
}
12211267

12221268
if [ "$PKG_MANAGER" = "dnf" ] || [ "$PKG_MANAGER" = "yum" ]; then
12231269
if [ "$PHPVM_LINUX_DISTRO" = "fedora" ]; then
1270+
major_minor=$(echo "$version" | cut -d. -f1,2)
12241271
phpvm_echo ""
12251272
phpvm_echo "PHP packages not found in default Fedora repositories."
12261273
phpvm_echo "To install PHP $version, you need to enable Remi's repository:"
@@ -1232,16 +1279,16 @@ suggest_repository_setup() {
12321279
phpvm_echo " sudo dnf install https://rpms.remirepo.net/fedora/remi-release-42.rpm"
12331280
fi
12341281
phpvm_echo ""
1235-
phpvm_echo " # Enable the repository"
1236-
phpvm_echo " sudo dnf config-manager --set-enabled remi"
1282+
phpvm_echo " # Enable the Remi repository"
1283+
_dnf_enable_repo_cmd "remi"
12371284
phpvm_echo ""
1238-
phpvm_echo " # Enable specific PHP version repository"
1239-
major_minor=$(echo "$version" | cut -d. -f1,2 | tr -d '.')
1240-
phpvm_echo " sudo dnf config-manager --set-enabled remi-php$major_minor"
1285+
phpvm_echo " # Enable the PHP module stream for version $major_minor"
1286+
phpvm_echo " sudo dnf module enable php:remi-${major_minor} -y"
12411287
phpvm_echo ""
12421288
phpvm_echo "After setting up the repositories, try: phpvm install $version"
12431289

12441290
elif [ "$PHPVM_LINUX_DISTRO" = "rhel" ] || [ "$PHPVM_LINUX_DISTRO" = "rocky" ] || [ "$PHPVM_LINUX_DISTRO" = "almalinux" ] || [ "$PHPVM_LINUX_DISTRO" = "centos" ]; then
1291+
major_minor=$(echo "$version" | cut -d. -f1,2 | tr -d '.')
12451292
phpvm_echo ""
12461293
phpvm_echo "PHP packages not found in default RHEL/CentOS repositories."
12471294
phpvm_echo "To install PHP $version, you need to enable EPEL and Remi repositories:"
@@ -1258,9 +1305,8 @@ suggest_repository_setup() {
12581305
fi
12591306
phpvm_echo ""
12601307
phpvm_echo " # Enable the repositories"
1261-
phpvm_echo " sudo dnf config-manager --set-enabled remi"
1262-
major_minor=$(echo "$version" | cut -d. -f1,2 | tr -d '.')
1263-
phpvm_echo " sudo dnf config-manager --set-enabled remi-php$major_minor"
1308+
_dnf_enable_repo_cmd "remi"
1309+
_dnf_enable_repo_cmd "remi-php${major_minor}"
12641310
phpvm_echo ""
12651311
phpvm_echo "After setting up the repositories, try: phpvm install $version"
12661312
fi
@@ -1612,16 +1658,28 @@ install_php_dnf() {
16121658

16131659
# Try to enable the module stream first
16141660
if command_exists dnf && dnf module list php > /dev/null 2>&1; then
1615-
phpvm_echo "Enabling PHP:${normalized_version} module stream..."
1616-
if ! run_with_sudo dnf module reset php -y 2> /dev/null; then
1617-
phpvm_warn "Failed to reset PHP module"
1618-
fi
1619-
if ! run_with_sudo dnf module enable php:"${normalized_version}" -y; then
1620-
phpvm_warn "Failed to enable PHP:${normalized_version} module stream"
1661+
local php_stream
1662+
php_stream=$(dnf_resolve_php_stream "$normalized_version")
1663+
if [ -z "$php_stream" ]; then
1664+
phpvm_warn "No php module stream found for PHP ${normalized_version}"
16211665
phpvm_warn "You may need Remi repository for this PHP version"
16221666
suggest_repository_setup "$version"
16231667
return "$PHPVM_EXIT_ERROR"
16241668
fi
1669+
phpvm_echo "Enabling php:${php_stream} module stream..."
1670+
if dnf_stream_enabled "$php_stream"; then
1671+
phpvm_debug "Module stream php:${php_stream} is already enabled β€” skipping reset/enable"
1672+
else
1673+
if ! run_with_sudo dnf module reset php -y 2> /dev/null; then
1674+
phpvm_warn "Failed to reset PHP module"
1675+
fi
1676+
if ! run_with_sudo dnf module enable "php:${php_stream}" -y; then
1677+
phpvm_warn "Failed to enable php:${php_stream} module stream"
1678+
phpvm_warn "You may need Remi repository for this PHP version"
1679+
suggest_repository_setup "$version"
1680+
return "$PHPVM_EXIT_ERROR"
1681+
fi
1682+
fi
16251683
fi
16261684

16271685
# Install the base PHP package
@@ -2018,9 +2076,22 @@ switch_to_version_php() {
20182076
elif [ "$PKG_MANAGER" = "dnf" ]; then
20192077
# Try dnf module approach for RHEL/Fedora
20202078
if command_exists dnf && dnf module list php > /dev/null 2>&1; then
2021-
phpvm_debug "Attempting to enable PHP $normalized_version module"
2022-
if ! run_with_sudo dnf module enable php:"$normalized_version" -y; then
2023-
phpvm_warn "Failed to enable PHP $normalized_version module."
2079+
local use_stream
2080+
use_stream=$(dnf_resolve_php_stream "$normalized_version")
2081+
if [ -n "$use_stream" ]; then
2082+
phpvm_debug "Attempting to enable php:${use_stream} module"
2083+
if dnf_stream_enabled "$use_stream"; then
2084+
phpvm_debug "Module stream php:${use_stream} already enabled"
2085+
else
2086+
if ! run_with_sudo dnf module reset php -y 2> /dev/null; then
2087+
phpvm_warn "Failed to reset PHP module"
2088+
fi
2089+
if ! run_with_sudo dnf module enable "php:${use_stream}" -y; then
2090+
phpvm_warn "Failed to enable php:${use_stream} module."
2091+
fi
2092+
fi
2093+
else
2094+
phpvm_warn "No php module stream found for PHP $normalized_version"
20242095
fi
20252096
fi
20262097
# After module enable, check if binary exists

β€Žtests/05_dnf.batsβ€Ž

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env bats
2+
# BATS test suite for phpvm - DNF module stream helpers (issue #20)
3+
# These tests use a PATH-based dnf stub to simulate "dnf module list php" output
4+
# without requiring a real Fedora/RHEL system.
5+
6+
load test_helper
7+
8+
# ── helpers ──────────────────────────────────────────────────────────────────
9+
10+
# Write a stub script that emulates "dnf module list php" for Remi streams.
11+
# The stub is placed first on PATH so phpvm.sh's dnf calls hit it.
12+
setup_dnf_stub_remi() {
13+
local stub_dir="$TEST_DIR/stub-remi"
14+
mkdir -p "$stub_dir"
15+
cat > "$stub_dir/dnf" <<'STUB'
16+
#!/bin/bash
17+
# Minimal dnf stub β€” only handles the subcommands phpvm uses
18+
case "$*" in
19+
"--version")
20+
echo "5.2.0"
21+
;;
22+
"module list php"|"module list php 2>/dev/null")
23+
cat <<'TABLE'
24+
php remi-7.4 common [d], devel, minimal PHP scripting language
25+
php remi-8.0 common [d], devel, minimal PHP scripting language
26+
php remi-8.1 common [d], devel, minimal PHP scripting language
27+
php remi-8.2 common [d], devel, minimal PHP scripting language
28+
php remi-8.3 common [d], devel, minimal PHP scripting language
29+
php remi-8.4 common [d], devel, minimal PHP scripting language
30+
php remi-8.5 common [d], devel, minimal PHP scripting language
31+
TABLE
32+
;;
33+
*)
34+
exit 1
35+
;;
36+
esac
37+
STUB
38+
chmod +x "$stub_dir/dnf"
39+
export PATH="$stub_dir:$PATH"
40+
}
41+
42+
# Remi stub with remi-8.2 already enabled ([e] in profile column)
43+
setup_dnf_stub_remi_enabled() {
44+
local stub_dir="$TEST_DIR/stub-remi-enabled"
45+
mkdir -p "$stub_dir"
46+
cat > "$stub_dir/dnf" <<'STUB'
47+
#!/bin/bash
48+
case "$*" in
49+
"--version")
50+
echo "5.2.0"
51+
;;
52+
"module list php"|"module list php 2>/dev/null")
53+
cat <<'TABLE'
54+
php remi-8.1 common [d], devel, minimal PHP scripting language
55+
php remi-8.2 [e] common [d], devel, minimal PHP scripting language
56+
php remi-8.3 common [d], devel, minimal PHP scripting language
57+
TABLE
58+
;;
59+
*)
60+
exit 1
61+
;;
62+
esac
63+
STUB
64+
chmod +x "$stub_dir/dnf"
65+
export PATH="$stub_dir:$PATH"
66+
}
67+
68+
# AppStream stub (plain X.Y stream names, DNF4)
69+
setup_dnf_stub_appstream() {
70+
local stub_dir="$TEST_DIR/stub-appstream"
71+
mkdir -p "$stub_dir"
72+
cat > "$stub_dir/dnf" <<'STUB'
73+
#!/bin/bash
74+
case "$*" in
75+
"--version")
76+
echo "4.14.0"
77+
;;
78+
"module list php"|"module list php 2>/dev/null")
79+
cat <<'TABLE'
80+
php 8.0 common [d], devel, minimal PHP scripting language
81+
php 8.1 common [d], devel, minimal PHP scripting language
82+
php 8.2 common [d], devel, minimal PHP scripting language
83+
TABLE
84+
;;
85+
*)
86+
exit 1
87+
;;
88+
esac
89+
STUB
90+
chmod +x "$stub_dir/dnf"
91+
export PATH="$stub_dir:$PATH"
92+
}
93+
94+
# ── dnf_resolve_php_stream ────────────────────────────────────────────────────
95+
96+
@test "dnf_resolve_php_stream returns remi-8.2 for version 8.2 on Remi Fedora" {
97+
setup_dnf_stub_remi
98+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
99+
result=$(dnf_resolve_php_stream "8.2")
100+
[ "$result" = "remi-8.2" ]
101+
}
102+
103+
@test "dnf_resolve_php_stream returns remi-8.1 for version 8.1 on Remi Fedora" {
104+
setup_dnf_stub_remi
105+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
106+
result=$(dnf_resolve_php_stream "8.1")
107+
[ "$result" = "remi-8.1" ]
108+
}
109+
110+
@test "dnf_resolve_php_stream returns empty string for unknown version on Remi" {
111+
setup_dnf_stub_remi
112+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
113+
result=$(dnf_resolve_php_stream "9.9")
114+
[ -z "$result" ]
115+
}
116+
117+
@test "dnf_resolve_php_stream returns 8.2 for version 8.2 on AppStream (DNF4)" {
118+
setup_dnf_stub_appstream
119+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
120+
result=$(dnf_resolve_php_stream "8.2")
121+
[ "$result" = "8.2" ]
122+
}
123+
124+
# ── dnf_stream_enabled ────────────────────────────────────────────────────────
125+
126+
@test "dnf_stream_enabled returns true when remi-8.2 is enabled" {
127+
setup_dnf_stub_remi_enabled
128+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
129+
run dnf_stream_enabled "remi-8.2"
130+
[ "$status" -eq 0 ]
131+
}
132+
133+
@test "dnf_stream_enabled returns false for non-enabled stream remi-8.1" {
134+
setup_dnf_stub_remi_enabled
135+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
136+
run dnf_stream_enabled "remi-8.1"
137+
[ "$status" -ne 0 ]
138+
}
139+
140+
# ── dnf_major_version ─────────────────────────────────────────────────────────
141+
142+
@test "dnf_major_version returns 5 for DNF5 stub" {
143+
setup_dnf_stub_remi
144+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
145+
result=$(dnf_major_version)
146+
[ "$result" = "5" ]
147+
}
148+
149+
@test "dnf_major_version returns 4 for DNF4 stub" {
150+
setup_dnf_stub_appstream
151+
source "$BATS_TEST_DIRNAME/../phpvm.sh"
152+
result=$(dnf_major_version)
153+
[ "$result" = "4" ]
154+
}

0 commit comments

Comments
Β (0)