diff --git a/.github/workflows/hooks-test.yml b/.github/workflows/hooks-test.yml new file mode 100644 index 0000000..a6a63f1 --- /dev/null +++ b/.github/workflows/hooks-test.yml @@ -0,0 +1,27 @@ +name: Test Plugin + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 + with: + go-version: '1.24.0' + cache: false + - name: Test plugin hooks with vfox's Lua interpreter + run: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/hooks_test.lua + - name: Test Windows configuration generation + run: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/windows_test.lua + - name: Test macOS OpenSSL selection + run: bash tests/openssl_test.sh diff --git a/.github/workflows/test-linux.yaml b/.github/workflows/test-linux.yaml index a109539..d83dd92 100644 --- a/.github/workflows/test-linux.yaml +++ b/.github/workflows/test-linux.yaml @@ -3,6 +3,8 @@ name: Test linux workflow on: pull_request: push: + branches: [main] + workflow_dispatch: schedule: - cron: 0 0 * * 5 @@ -15,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: install vfox (Linux) run: | echo "deb [trusted=yes] https://apt.fury.io/versionfox/ /" | sudo tee /etc/apt/sources.list.d/versionfox.list @@ -30,11 +32,16 @@ jobs: zip -r php.zip ./ - name: Test + env: + MAKEFLAGS: -j2 run: | vfox add -s php.zip vfox install php@latest - vfox use -p php@$(vfox list php | sed -n 's/-> v//p') eval "$(vfox activate bash)" + vfox use -p php@latest + eval "$(vfox env --shell bash)" + php_path=$(command -v php) + [[ "$php_path" == "$(pwd -P)/.vfox/sdks/php/bin/php" ]] php -v php -m composer -V diff --git a/.github/workflows/test-macos.yaml b/.github/workflows/test-macos.yaml index c1e9b3a..fbc8186 100644 --- a/.github/workflows/test-macos.yaml +++ b/.github/workflows/test-macos.yaml @@ -3,6 +3,8 @@ name: Test macos workflow on: pull_request: push: + branches: [main] + workflow_dispatch: schedule: - cron: 0 0 * * 5 @@ -15,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: install vfox (MacOS) run: | brew tap version-fox/tap @@ -26,15 +28,20 @@ jobs: zip -r php.zip ./ - name: Install packages - run: brew install autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libxslt libzip pkg-config re2c zlib + run: brew install autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libxslt libzip openssl@3 pkg-config re2c zlib - name: Test + env: + MAKEFLAGS: -j2 shell: bash run: | vfox add -s php.zip vfox install php@latest - vfox use -p php@$(vfox list php | sed -n 's/-> v//p') eval "$(vfox activate bash)" + vfox use -p php@latest + eval "$(vfox env --shell bash)" + php_path=$(command -v php) + [[ "$php_path" == "$(pwd -P)/.vfox/sdks/php/bin/php" ]] php -v php -m composer -V diff --git a/.github/workflows/test-windows.yaml b/.github/workflows/test-windows.yaml index bf2661e..60255b6 100644 --- a/.github/workflows/test-windows.yaml +++ b/.github/workflows/test-windows.yaml @@ -3,6 +3,8 @@ name: Test windows workflow on: pull_request: push: + branches: [main] + workflow_dispatch: schedule: - cron: 0 0 * * 5 @@ -15,11 +17,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: install vfox and test (Windows) shell: pwsh run: | + $PSNativeCommandUseErrorActionPreference = $true Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression scoop install vfox @@ -27,8 +30,9 @@ jobs: vfox -v vfox add -s php.zip vfox install php@latest - vfox use -p php@$(vfox list php | sed -n 's/-> v//p') + vfox use -p php@latest Invoke-Expression "$(vfox activate pwsh)" php -v php -m + if (-not ((php -m) -contains "openssl")) { throw "OpenSSL extension was not loaded" } composer -V diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index 28bd6fe..bea0988 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ vfox install php@8.4.5 vfox install php@8.4.5-nts ``` +The version list combines current and archived Windows binaries, sorted newest +first. Plain Windows versions select thread-safe (TS) builds; `-nts` selects NTS. +Linux and macOS source versions come directly from PHP's official JSON API. +Network failures report the upstream URL instead of returning an empty list. + ## Prerequirements PHP installation requires some dependencies. Please install the dependencies based on the error messages, or refer to [.github/workflows/test-\*.yaml](https://github.com/version-fox/vfox-php/tree/main/.github/workflows) for guidance. @@ -26,9 +31,17 @@ PHP installation requires some dependencies. Please install the dependencies bas To install PHP on macOS, you'll need a set of packages installed via homebrew. ```shell -brew install autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip pkg-config re2c zlib +brew install autoconf automake bison freetype gd gettext icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip openssl@3 pkg-config re2c zlib ``` +PHP 8.1 and newer use `openssl@3` on macOS. Older PHP versions require a +compatible older OpenSSL installation; the build reports a missing dependency +instead of silently selecting OpenSSL 3 or omitting HTTPS support. Homebrew no +longer supplies `openssl@1.1` through its normal supported formulae. See the +[PHP OpenSSL compatibility requirements](https://www.php.net/manual/en/openssl.requirements.php). +If you supply `PHP_CONFIGURE_OPTIONS`, you remain responsible for configuring +the dependency paths; automatic macOS dependency selection is bypassed. + There's also a set of optional packages which enable additional extensions to be enabled: ```shell diff --git a/bin/install b/bin/install index be9a46d..84b400b 100755 --- a/bin/install +++ b/bin/install @@ -7,7 +7,8 @@ install_php() { local install_path=$1 local source_path="${install_path}_source" - local configure_options="$(construct_configure_options $install_path)" + local configure_options + configure_options="$(construct_configure_options "$install_path")" || return 1 local operating_system=$(uname -a) @@ -20,7 +21,11 @@ install_php() { local libedit_path=$(homebrew_package_path libedit) local libxml2_path=$(homebrew_package_path libxml2) local libxslt_path=$(homebrew_package_path libxslt) - local openssl_path=$(homebrew_package_path openssl@1.1) + local openssl_path + # Explicit configure options own their dependency paths, as before. + if [ -z "${PHP_CONFIGURE_OPTIONS:-}" ]; then + openssl_path=$(homebrew_openssl_path) || return 1 + fi if [ -n "$bison_path" ]; then export "PATH=${bison_path}/bin:${PATH}" @@ -133,7 +138,9 @@ construct_configure_options() { --without-snmp" if [ "$PHP_CONFIGURE_OPTIONS" = "" ]; then - local configure_options="$(os_based_configure_options) $global_config" + local platform_options + platform_options=$(os_based_configure_options) || return 1 + local configure_options="$platform_options $global_config" else local configure_options="$PHP_CONFIGURE_OPTIONS $global_config" fi @@ -163,6 +170,28 @@ homebrew_package_path() { fi } +homebrew_openssl_path() { + # PHP 7.1-8.0 requires OpenSSL < 3.0; PHP 8.1+ supports OpenSSL 3. + # See https://www.php.net/manual/en/openssl.requirements.php + local major minor formula path + if [[ ! "${PHP_VERSION:-}" =~ ^([0-9]+)\.([0-9]+)\. ]]; then + echo "ERROR: Cannot determine PHP version for OpenSSL selection" >&2 + return 1 + fi + major=${BASH_REMATCH[1]} + minor=${BASH_REMATCH[2]} + formula=openssl@1.1 + if (( major > 8 || (major == 8 && minor >= 1) )); then + formula=openssl@3 + fi + path=$(homebrew_package_path "$formula") + if [ -z "$path" ]; then + echo "ERROR: PHP $PHP_VERSION requires $formula; install a compatible dependency before building." >&2 + return 1 + fi + echo "$path" +} + exit_if_homebrew_not_installed() { if [ "$(brew --version 2>/dev/null)" = "" ]; then echo "ERROR: Please install homebrew for OSX" @@ -190,7 +219,8 @@ os_based_configure_options() { local libxml2_path=$(homebrew_package_path libxml2) local libxslt_path=$(homebrew_package_path libxslt) local libzip_path=$(homebrew_package_path libzip) - local openssl_path=$(homebrew_package_path openssl@1.1) + local openssl_path + openssl_path=$(homebrew_openssl_path) || return 1 local readline_path=$(homebrew_package_path readline) local webp_path=$(homebrew_package_path webp) local zlib_path=$(homebrew_package_path zlib) @@ -298,5 +328,8 @@ os_based_configure_options() { echo $configure_options } -install_php $1 -install_composer $1 +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + PHP_VERSION=${2:-$(sed -n 's/^#define PHP_VERSION "\([^"]*\)"/\1/p' "$1/main/php_version.h")} + install_php "$1" + install_composer "$1" +fi diff --git a/hooks/available.lua b/hooks/available.lua index 59e5027..f03aef2 100644 --- a/hooks/available.lua +++ b/hooks/available.lua @@ -1,82 +1,5 @@ -local http = require('http') -local html = require('html') -local util = require('util') -require('constants') +local releases = require("releases") ---- Return all available versions provided by this plugin ---- @param ctx table Empty table used as context, for future extension ---- @return table Descriptions of available versions and accompanying tool descriptions function PLUGIN:Available(ctx) - if RUNTIME.osType == 'windows' then - return GetReleaseListForWindows() - else - return GetReleaseListForLinux() - end -end - -function GetReleaseListForWindows() - local result = {} - local urls = { WIN_RELEASES_URL, WIN_RELEASES_URL_LTS } - - for _, url in ipairs(urls) do - local resp, err = http.get({ url = url }) - - if resp then - local doc = html.parse(resp.body) - local versions = {} - doc:find('a'):each(function(i, selection) - local versionStr = selection:text() - table.insert(versions, versionStr) - end) - -- TODO like this because for some reason sorting it at the end resets is_from_lts to false - table.sort(versions, function(a, b) - return util.compare_versions(a, b) > 0 - end) - for _, versionStr in ipairs(versions) do - if util.filter_windows_version(versionStr) then - local versions = util.split_string(versionStr, '-') - if util.compare_versions(versions[2], "5.3.2") >= 0 then - local entry = { - version = (versions[3] ~= "nts") and versions[2] or versions[2] .. "-nts", - name = versionStr - } - - entry.is_from_lts = (url == WIN_RELEASES_URL_LTS) - table.insert(result, entry) - end - end - end - end - end - - return result -end - -function GetReleaseListForLinux() - local result = {} - local urls = { RELEASES_URL, RELEASES_URL_LTS } - - for _, url in ipairs(urls) do - local resp, err = http.get({ url = url }) - local is_from_lts = (url == RELEASES_URL_LTS) - - if resp then - local doc = html.parse(resp.body) - local query = "#layout-content " .. (is_from_lts and "h3" or "h2") - doc:find(query):each(function(i, selection) - local versionStr = is_from_lts and selection:attr("id") or selection:text() - versionStr = versionStr:gsub("^v", "") - if util.compare_versions(versionStr, "5.3.2") >= 0 then - table.insert(result, { - version = versionStr, - }) - end - end) - end - end - - table.sort(result, function(a, b) - return util.compare_versions(a.version, b.version) > 0 - end) - return result + return releases.available() end diff --git a/hooks/post_install.lua b/hooks/post_install.lua index 9e11736..ee269c8 100644 --- a/hooks/post_install.lua +++ b/hooks/post_install.lua @@ -10,7 +10,7 @@ function PLUGIN:PostInstall(ctx) if RUNTIME.osType == 'windows' then InstallComposerForWin(path) else - CompileInstallPHP(path) + CompileInstallPHP(path, sdkInfo.version) end end @@ -19,7 +19,14 @@ function InstallComposerForWin(path) if err ~= nil then error(err) end - content = content:gsub(';%s*extension_dir = "ext"', 'extension_dir = "./ext"') + -- Use an absolute extension_dir so php.exe can locate php_openssl.dll + -- regardless of which CWD it was launched from. The composer-setup + -- step below invokes php.exe from the user's shell CWD, so a relative + -- "./ext" path resolves to the wrong directory and fails to load + -- openssl, breaking the HTTPS download Composer needs. + local ext_dir = (path .. "\\ext"):gsub("\\", "/") + content = content:gsub(';%s*extension_dir = "ext"', + function() return 'extension_dir = "' .. ext_dir .. '"' end) content = content:gsub(';extension=openssl', 'extension=openssl') content = content:gsub(';extension=php_openssl.dll', 'extension=php_openssl.dll') _, err = util.write_file(path .. '\\php.ini', content) @@ -68,9 +75,12 @@ function InstallComposerForWin(path) util.write_file(path .. '\\composer.bat', '@php "%~dp0composer.phar" %*') end -function CompileInstallPHP(path) +function CompileInstallPHP(path, version) + if not version:match("^%d+%.%d+%.%d+$") then + error("Invalid PHP source version: " .. tostring(version)) + end os.execute('chmod +x ' .. RUNTIME.pluginDirPath .. '/bin/install') - local code = os.execute(RUNTIME.pluginDirPath .. '/bin/install ' .. path) + local code = os.execute(RUNTIME.pluginDirPath .. '/bin/install ' .. path .. ' ' .. version) if code ~= 0 then error('Compilation Failure.') end diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index f9fa16e..dbfbd8b 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -1,74 +1,27 @@ -local http = require('http') -local json = require('json') -local util = require('util') -require('constants') +local util = require("util") ---- Returns some pre-installed information, such as version number, download address, local files, etc. ---- If checksum is provided, vfox will automatically check it for you. ---- @param ctx table ---- @field ctx.version string User-input version ---- @return table Version information function PLUGIN:PreInstall(ctx) local version = ctx.version - local lists = self:Available({}) - if version == 'latest' or version == '' then - version = lists[1].version - end - - local versions = {} - for _, value in pairs(lists) do - if util.starts_with(value.version, version .. '.') then - versions = value - end - if value.version == version then - versions = value - end - if next(versions) ~= nil then - break - end + local versions = self:Available({}) + if #versions == 0 then + error("No PHP releases available") end - if next(versions) == nil then - error('version not found for provided version ' .. version) + if version == "latest" or version == "" then + return versions[1] end - - if RUNTIME.osType == 'windows' then - return GetReleaseForWindows(versions) - else - return GetReleaseForLinux(versions) + if type(version) ~= "string" then + error("PHP version must be provided") end -end - -function GetReleaseForWindows(versions) - url = WIN_RELEASES_URL .. versions.name - - if (versions.is_from_lts) then - url = WIN_RELEASES_URL_LTS .. versions.name + -- Exact matches must win over prefixes, especially for Windows TS/NTS. + for _, release in ipairs(versions) do + if release.version == version then + return release + end end - return { - version = versions.version, - url = url, - } -end - -function GetReleaseForLinux(versions) - local resp, err = http.get({ - url = URL .. "/releases/index.php?json&version=" .. versions.version - }) - local data = json.decode(resp.body) - - local filename, md5, sha256 = "", "", "" - for _, s in pairs(data["source"]) do - if util.ends_with(s.filename, ".tar.gz") then - filename = s.filename - md5 = s.md5 - sha256 = s.sha256 - break + for _, release in ipairs(versions) do + if util.starts_with(release.version, version .. ".") then + return release end end - return { - version = versions.version, - url = URL .. "/distributions/" .. filename, - sha256 = sha256, - md5 = md5 - } + error("PHP version not found: " .. version) end diff --git a/lib/releases.lua b/lib/releases.lua new file mode 100644 index 0000000..2c26833 --- /dev/null +++ b/lib/releases.lua @@ -0,0 +1,116 @@ +local http = require("http") +local json = require("json") +local html = require("html") +local util = require("util") +local releases = {} + +local function fetch(url) + local response, err = http.get({ url = url }) + if err ~= nil or response == nil then + error("Failed to fetch PHP releases from " .. url .. ": " .. tostring(err or "empty response")) + end + if response.status_code ~= 200 then + error("Failed to fetch PHP releases from " .. url .. ": HTTP " .. tostring(response.status_code)) + end + return response.body +end + +local function sort(result) + table.sort(result, function(a, b) + local order = util.compare_versions(a.version:match("^[%d.]+"), b.version:match("^[%d.]+")) + if order ~= 0 then + return order > 0 + end + -- Plain versions are thread-safe on Windows; prefer them for latest. + return a.version < b.version + end) + if #result == 0 then + error("No PHP releases found for " .. RUNTIME.osType .. "/" .. RUNTIME.archType) + end + return result +end + +local function windows() + local arch = ({ amd64 = "x64", ["386"] = "x86", arm64 = "arm64" })[RUNTIME.archType] + if not arch then + error("Unsupported PHP architecture: " .. tostring(RUNTIME.archType)) + end + local result, seen = {}, {} + -- Current releases win when the same version also exists in the archive. + for _, base in ipairs({ + "https://windows.php.net/downloads/releases/", + "https://windows.php.net/downloads/releases/archives/", + }) do + html.parse(fetch(base)):find("a"):each(function(_, selection) + local filename = (selection:attr("href") or ""):match("([^/]+)$") or "" + local version, variant, fileArch = + filename:match("^php%-(%d+%.%d+%.%d+)(.-)%-Win32%-[Vv][CcSs]%d+%-(%w+)%.zip$") + if not version or fileArch ~= arch then + return + end + local rebuild = variant:match("^%-(%d+)$") or variant:match("^%-nts%-(%d+)$") + if variant ~= "" and variant ~= "-nts" and not rebuild then + return + end + local nts = variant:match("^%-nts") ~= nil + rebuild = tonumber(rebuild) or 0 + if util.compare_versions(version, "5.3.2") < 0 then + return + end + version = version .. (nts and "-nts" or "") + if not seen[version] then + local entry = { version = version, url = base .. filename, note = nts and "NTS" or "TS" } + seen[version] = { entry = entry, base = base, rebuild = rebuild } + table.insert(result, entry) + elseif seen[version].base == base and rebuild > seen[version].rebuild then + seen[version].entry.url = base .. filename + seen[version].rebuild = rebuild + end + end) + end + return sort(result) +end + +local function source() + local result, seen = {}, {} + for _, major in ipairs({ 8, 7, 5 }) do + local url = "https://www.php.net/releases/index.php?json&max=10000&version=" .. major + local decoded = json.decode(fetch(url)) + if type(decoded) ~= "table" then + error("Invalid PHP release index from " .. url) + end + for version, release in pairs(decoded) do + if + type(version) == "string" + and version:match("^%d+%.%d+%.%d+$") + and util.compare_versions(version, "5.3.2") >= 0 + and type(release) == "table" + and type(release.source) == "table" + and not seen[version] + then + for _, file in ipairs(release.source) do + if file.filename == "php-" .. version .. ".tar.gz" then + seen[version] = true + table.insert(result, { + version = version, + url = "https://www.php.net/distributions/" .. file.filename, + sha256 = file.sha256, + md5 = file.md5, + }) + break + end + end + end + end + end + return sort(result) +end + +function releases.available() + if RUNTIME.osType == "windows" then + return windows() + end + return source() +end + +return releases diff --git a/tests/hooks_test.lua b/tests/hooks_test.lua new file mode 100644 index 0000000..2f547e3 --- /dev/null +++ b/tests/hooks_test.lua @@ -0,0 +1,103 @@ +package.path = "./lib/?.lua;" .. package.path +PLUGIN = {} +RUNTIME = { osType = "windows", archType = "amd64" } +local responses, state = {}, {} +package.preload.http = function() + return { + get = function(args) + if state.failure then + return state.failure.response, state.failure.error + end + return { status_code = 200, body = assert(responses[args.url], "unexpected URL " .. args.url) } + end, + } +end +package.preload.json = function() + return { + decode = function(body) + return body + end, + } +end +package.preload.html = function() + return { + parse = function(body) + return { + find = function() + return { + each = function(_, fn) + for i, filename in ipairs(body) do + fn(i, { + attr = function() + return filename + end, + text = function() + return filename + end, + }) + end + end, + } + end, + } + end, + } +end +local current = "https://windows.php.net/downloads/releases/" +local archives = current .. "archives/" +responses[current] = { + "/downloads/releases/php-8.4.2-nts-Win32-vs17-x64.zip", + "/downloads/releases/php-8.4.2-Win32-vs17-x64.zip", + "/downloads/releases/php-8.4.2-Win32-vs17-x86.zip", + "/downloads/releases/php-8.4.2-debug-pack-Win32-vs17-x64.zip", +} +responses[archives] = { + "php-5.6.16-nts-Win32-VC11-x64.zip", + "php-8.4.2-Win32-vs17-x64.zip", + "php-8.3.15-Win32-vs16-x64.zip", + "php-8.4.1-nts-Win32-vs17-x64.zip", + "php-7.2.33-1-Win32-VC15-x64.zip", + "php-7.2.33-Win32-VC15-x64.zip", +} +for _, major in ipairs({ 5, 7, 8 }) do + responses["https://www.php.net/releases/index.php?json&max=10000&version=" .. major] = {} +end +responses["https://www.php.net/releases/index.php?json&max=10000&version=8"] = { + ["8.4.2"] = { source = { { filename = "php-8.4.2.tar.gz", sha256 = string.rep("a", 64) } } }, + ["8.3.15"] = { source = { { filename = "php-8.3.15.tar.gz", sha256 = string.rep("b", 64) } } }, + ["8.5.0RC1"] = { source = { { filename = "php-8.5.0RC1.tar.gz" } } }, +} +dofile("hooks/available.lua") +dofile("hooks/pre_install.lua") +local list = PLUGIN:Available({}) +assert(list[1].version == "8.4.2", "latest must be current TS, got " .. tostring(list[1].version)) +assert(#list == 6, #list) +assert(PLUGIN:PreInstall({ version = "7.2.33" }).url == archives .. "php-7.2.33-1-Win32-VC15-x64.zip") +assert(PLUGIN:PreInstall({ version = "latest" }).url == current .. "php-8.4.2-Win32-vs17-x64.zip") +assert(PLUGIN:PreInstall({ version = "8.4.2-nts" }).version == "8.4.2-nts") +assert(PLUGIN:PreInstall({ version = "8.3" }).url == archives .. "php-8.3.15-Win32-vs16-x64.zip") +RUNTIME.archType = "386" +assert(#PLUGIN:Available({}) == 1) +RUNTIME.osType = "linux" +list = PLUGIN:Available({}) +assert(#list == 2 and list[1].version == "8.4.2") +local install = PLUGIN:PreInstall({ version = "8.4" }) +assert(install.url == "https://www.php.net/distributions/php-8.4.2.tar.gz") +assert(install.sha256 == string.rep("a", 64)) +local function fails(fn, expected) + local ok, err = pcall(fn) + assert(not ok and tostring(err):find(expected, 1, true), tostring(err)) +end +state.failure = { error = "timeout" } +fails(function() + PLUGIN:Available({}) +end, "timeout") +state.failure = { response = { status_code = 503 } } +fails(function() + PLUGIN:PreInstall({ version = "latest" }) +end, "503") +state.failure = nil +fails(function() + PLUGIN:PreInstall({ version = "99.0.0" }) +end, "not found") +print("PHP release discovery, ordering, architecture and failure cases passed") diff --git a/tests/openssl_test.sh b/tests/openssl_test.sh new file mode 100644 index 0000000..f4186c0 --- /dev/null +++ b/tests/openssl_test.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail +source bin/install + +installed_three=yes +installed_one=yes +homebrew_package_path() { + if [[ "$1" == openssl@3 && "$installed_three" == yes ]] || [[ "$1" == openssl@1.1 && "$installed_one" == yes ]]; then + echo "/fake/$1" + fi +} +for version in 8.1.0 8.4.2 8.5.0; do + PHP_VERSION=$version + [[ "$(homebrew_openssl_path)" == /fake/openssl@3 ]] +done +for version in 7.4.33 8.0.30; do + PHP_VERSION=$version + [[ "$(homebrew_openssl_path)" == /fake/openssl@1.1 ]] +done +PHP_VERSION=7.4.33 +installed_one=no +if homebrew_openssl_path 2>/dev/null; then + echo "Old PHP must not silently use OpenSSL 3" >&2; exit 1 +fi +PHP_VERSION=8.4.2 +installed_three=no +if homebrew_openssl_path 2>/dev/null; then + echo "Missing OpenSSL must report a dependency error" >&2; exit 1 +fi +echo "OpenSSL compatibility and missing dependency cases passed" + +# Custom configure options must still work without a Homebrew OpenSSL formula. +PHP_CONFIGURE_OPTIONS=--with-openssl=/custom/openssl +options=$(construct_configure_options /tmp/php-test) +[[ "$options" == *--with-openssl=/custom/openssl* ]] +echo "Explicit configure options preserved" diff --git a/tests/windows_test.lua b/tests/windows_test.lua new file mode 100644 index 0000000..0aaf948 --- /dev/null +++ b/tests/windows_test.lua @@ -0,0 +1,33 @@ +package.path = "./lib/?.lua;" .. package.path +PLUGIN = {} +RUNTIME = { osType = "windows" } +local state = { files = {}, commands = {} } +local util = require("util") +util.read_file = function() + return ';extension_dir = "ext"\n;extension=openssl\n;extension=php_openssl.dll\n' +end +util.write_file = function(path, content) + state.files[path] = content + return true +end +package.preload.http = function() + return { + get = function() + return { status_code = 200, body = "composer installer" } + end, + } +end +os.execute = function(command) + state.commands[#state.commands + 1] = command + return 0 +end +os.remove = function() end +dofile("hooks/post_install.lua") +local path = "C:\\SDKs\\100% PHP" +PLUGIN:PostInstall({ sdkInfo = { php = { path = path, version = "8.4.2" } } }) +local ini = assert(state.files[path .. "\\php.ini"]) +assert(ini:find('extension_dir = "C:/SDKs/100% PHP/ext"', 1, true), ini) +assert(ini:find("\nextension=openssl\n", 1, true), ini) +assert(ini:find("\nextension=php_openssl.dll\n", 1, true), ini) +assert(#state.commands == 1) +print("Windows PHP extension path substitution passed")