diff --git a/.github/workflows/e2e_test.yaml b/.github/workflows/e2e_test.yaml index 26aa7c9..7513913 100644 --- a/.github/workflows/e2e_test.yaml +++ b/.github/workflows/e2e_test.yaml @@ -60,7 +60,7 @@ jobs: - name: add vfox-lua plugin if: runner.os == 'Windows' run: | - vfox add --source https://github.com/${{ github.repository_owner }}/vfox-lua/archive/$env:GITHUB_REF.zip lua + vfox add --source "https://github.com/${{ github.repository_owner }}/vfox-lua/archive/${env:GITHUB_REF}.zip" lua - name: add vfox-lua plugin if: runner.os != 'Windows' @@ -269,3 +269,53 @@ jobs: $HOME/.local/bin/mise exec -- luarocks --version $HOME/.local/bin/mise exec -- luarocks install luacheck $HOME/.local/bin/mise exec -- luarocks list | grep luacheck + + windows_luabinaries_tests: + strategy: + matrix: + lua_version: [5.5.0, 5.4.8, 5.3.6] + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '^1.24.2' + + - name: build & install vfox + shell: pwsh + run: | + git clone https://github.com/version-fox/vfox.git + cd vfox + go build -o vfox.exe + echo "$pwd" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + ./vfox.exe -version + + - name: add vfox-lua plugin + shell: pwsh + run: | + vfox add --source "https://github.com/${{ github.repository_owner }}/vfox-lua/archive/${env:GITHUB_REF}.zip" lua + + - name: install Lua from LuaBinaries by vfox-lua plugin + shell: pwsh + env: + LUA_VERSION: "${{ matrix.lua_version }}" + VFOX_LUA_WINDOWS_LUABINARIES: "1" + run: | + vfox install "lua@$env:LUA_VERSION" + Invoke-Expression "$(vfox activate pwsh)" + vfox use -g "lua@$env:LUA_VERSION" + Invoke-Expression "$(vfox activate pwsh)" + echo "===============PATH===============" + echo $env:PATH + echo "===============PATH===============" + $luaVersion = lua.exe -v + $luacVersion = luac.exe -v + if ($luaVersion -notmatch "Lua $env:LUA_VERSION") { + Write-Output "lua@$env:LUA_VERSION installed from LuaBinaries failed!" + exit 1 + } + if ($luacVersion -notmatch "Lua $env:LUA_VERSION") { + Write-Output "luac@$env:LUA_VERSION installed from LuaBinaries failed!" + exit 1 + } diff --git a/README.md b/README.md index 10ca310..420cdc1 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Lua [vfox](https://github.com/version-fox) plugin. Use the vfox to manage multip - GCC compiler - Make +Optional on Windows: +- Set `VFOX_LUA_WINDOWS_LUABINARIES=1` to install a prebuilt LuaBinaries package instead of compiling from source +- Supported LuaBinaries versions: `5.5.0`, `5.4.8`, `5.3.6`, `5.2.4` + ## Usage ### Install with vfox @@ -38,6 +42,15 @@ vfox install lua@5.4.7 vfox use -g lua@5.4.7 ``` +On Windows, you can opt into prebuilt LuaBinaries packages instead of the default MSYS2 source build: + +```powershell +$env:VFOX_LUA_WINDOWS_LUABINARIES=1 +vfox install lua@5.5.0 +vfox use -g lua@5.5.0 +lua -v +``` + ### Install with mise The vfox-lua plugin can also be used through [mise](https://mise.jdx.dev/), which supports vfox plugins. @@ -92,6 +105,8 @@ luarocks install luacheck 3. On Windows, use `PowerShell` to install Lua. +4. `VFOX_LUA_WINDOWS_LUABINARIES=1` is a Windows-only opt-in. If the requested version is not published by LuaBinaries, the install will fail and you should use the default source-build flow instead. The archive is downloaded from SourceForge's mirror autoselect over HTTPS; checksum verification is currently skipped because the redirected mirrors don't yield a stable hash. Prefer the default source-build flow if you require an integrity check. + ## Known Issues - Lua versions 5.0 and earlier cannot be installed on Linux. diff --git a/hooks/env_keys.lua b/hooks/env_keys.lua index 5903be1..610ccb0 100644 --- a/hooks/env_keys.lua +++ b/hooks/env_keys.lua @@ -5,7 +5,7 @@ function PLUGIN:EnvKeys(ctx) local sdkInfo = ctx.sdkInfo["lua"] local version = sdkInfo.version - local installDir = sdkInfo.path + local installDir = ctx.path local shortVersion = string.match(version, "^(%d+%.%d+)") @@ -16,6 +16,21 @@ function PLUGIN:EnvKeys(ctx) }, } + -- LuaBinaries installs place lua.exe in the install root next to the DLL; MSYS2 source + -- builds only populate bin/. Detect the LuaBinaries layout by probing the file so PATH + -- stays correct on `vfox use` even when VFOX_LUA_WINDOWS_LUABINARIES (an install-time + -- opt-in) is no longer exported in the activation shell. + if RUNTIME.osType == "windows" then + local rootLua = io.open(installDir .. "/lua.exe", "r") + if rootLua ~= nil then + rootLua:close() + table.insert(envs, 1, { + key = "PATH", + value = installDir, + }) + end + end + local luarocksBin = installDir .. "/luarocks/bin" local f = io.open(luarocksBin, "r") if f ~= nil then diff --git a/hooks/post_install.lua b/hooks/post_install.lua index fb5834d..4b3b7c8 100644 --- a/hooks/post_install.lua +++ b/hooks/post_install.lua @@ -1,3 +1,5 @@ +local Utils = require("utils") + --- Extension point, called after PreInstall, can perform additional operations, --- such as file operations for the SDK installation directory or compile source code function CheckBuildTools(osType) @@ -22,6 +24,69 @@ Make sure make.exe and gcc.exe are in the System $env:PATH in PowerShell. end end +local function copy_file(src, dst) + local input = io.open(src, "rb") + if input == nil then + return false + end + local content = input:read("*a") + input:close() + + local output = io.open(dst, "wb") + if output == nil then + return false + end + output:write(content) + output:close() + return true +end + +local function file_exists(path) + local f = io.open(path, "rb") + if f == nil then + return false + end + f:close() + return true +end + +local function InstallWindowsLuaBinaries(path, lua_version) + local pkg_meta = Utils.get_windows_luabinaries_package(lua_version) + if pkg_meta == nil then + error("LuaBinaries package metadata missing for version " .. lua_version) + end + + -- LuaBinaries archives expand executables (lua54.exe, luac54.exe, ...) into the install + -- root next to the DLL. We add aliases (lua.exe, luac.exe, wlua.exe) in the same folder + -- so users can invoke them by their canonical names. env_keys.lua already prepends the + -- install root to PATH, so a separate bin/ directory isn't needed. + local lua_exe = path .. "\\" .. pkg_meta.executable_prefix .. ".exe" + if not file_exists(lua_exe) then + error("LuaBinaries executable not found at " .. lua_exe) + end + + local copies = { + { src = lua_exe, dst = path .. "\\lua.exe" }, + } + + local luac_prefix = "luac" .. string.sub(pkg_meta.executable_prefix, 4) + local luac_exe = path .. "\\" .. luac_prefix .. ".exe" + if file_exists(luac_exe) then + table.insert(copies, { src = luac_exe, dst = path .. "\\luac.exe" }) + end + + local wlua_exe = path .. "\\" .. pkg_meta.wlua_prefix .. ".exe" + if file_exists(wlua_exe) then + table.insert(copies, { src = wlua_exe, dst = path .. "\\wlua.exe" }) + end + + for _, item in ipairs(copies) do + if not copy_file(item.src, item.dst) then + error("failed to copy " .. item.src .. " to " .. item.dst) + end + end +end + function PLUGIN:PostInstall(ctx) --- ctx.rootPath SDK installation directory @@ -32,6 +97,11 @@ function PLUGIN:PostInstall(ctx) local lua_version = sdkInfo.version print(string.format("os type: %s, lua installed path: %s", RUNTIME.osType, path)) + if RUNTIME.osType == "windows" and Utils.use_windows_luabinaries() then + InstallWindowsLuaBinaries(path, lua_version) + return + end + CheckBuildTools(RUNTIME.osType) -- TODO: support install luajit local status diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index 77462f8..82ca34f 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -13,6 +13,26 @@ function PLUGIN:PreInstall(ctx) local lua_version = ctx.version local download_url + if RUNTIME.osType == "windows" and Utils.use_windows_luabinaries() then + local pkg_meta = Utils.get_windows_luabinaries_package(lua_version) + if pkg_meta == nil then + error("LuaBinaries does not provide a Windows binary for version " .. + lua_version .. ". Disable VFOX_LUA_WINDOWS_LUABINARIES or choose one of: " .. + Utils.get_windows_luabinaries_versions_text() .. ".") + end + + print("lua download url: " .. pkg_meta.url) + -- LuaBinaries is distributed via SourceForge's mirror autoselect, which redirects to + -- a rotating set of mirrors. vfox computed checksums sometimes hashed the redirect HTML + -- instead of the archive bytes, so the integrity check was unreliable. Until upstream + -- publishes signed checksums we can pin, the opt-in install relies on HTTPS only — + -- documented as a trade-off in the README. + return { + version = lua_version, + url = pkg_meta.url, + } + end + local v, checksum = Utils.get_version_info(lua_version) if not v then error("Version " .. lua_version .. " not found in https://www.lua.org/ftp/.") @@ -27,4 +47,4 @@ function PLUGIN:PreInstall(ctx) url = download_url, sha256 = checksum } -end \ No newline at end of file +end diff --git a/lib/utils.lua b/lib/utils.lua index 56f7e88..88356d5 100644 --- a/lib/utils.lua +++ b/lib/utils.lua @@ -1,6 +1,36 @@ local http = require("http") local lua_utils = {} +local windows_luabinaries_packages = { + ["5.5.0"] = { + archive_name = "lua-5.5.0_Win64_bin.zip", + relative_path = "5.5.0/Tools%20Executables/lua-5.5.0_Win64_bin.zip", + executable_prefix = "lua55", + wlua_prefix = "wlua55", + dll_name = "lua55.dll", + }, + ["5.4.8"] = { + archive_name = "lua-5.4.8_Win64_bin.zip", + relative_path = "5.4.8/Tools%20Executables/lua-5.4.8_Win64_bin.zip", + executable_prefix = "lua54", + wlua_prefix = "wlua54", + dll_name = "lua54.dll", + }, + ["5.3.6"] = { + archive_name = "lua-5.3.6_Win64_bin.zip", + relative_path = "5.3.6/Tools%20Executables/lua-5.3.6_Win64_bin.zip", + executable_prefix = "lua53", + wlua_prefix = "wlua53", + dll_name = "lua53.dll", + }, + ["5.2.4"] = { + archive_name = "lua-5.2.4_Win64_bin.zip", + relative_path = "5.2.4/Tools%20Executables/lua-5.2.4_Win64_bin.zip", + executable_prefix = "lua52", + wlua_prefix = "wlua52", + dll_name = "lua52.dll", + }, +} local function parse_version_line(line) return string.match(line, "([^,]+),([^,]+)") @@ -42,6 +72,50 @@ function lua_utils.get_version_info(lua_version) return nil, nil end +function lua_utils.use_windows_luabinaries() + local flag = os.getenv("VFOX_LUA_WINDOWS_LUABINARIES") + return flag ~= nil and flag ~= "" and flag ~= "0" and flag ~= "false" +end + +function lua_utils.get_windows_luabinaries_versions() + local versions = {} + for version, _ in pairs(windows_luabinaries_packages) do + table.insert(versions, version) + end + table.sort(versions, function(a, b) + return a > b + end) + return versions +end + +function lua_utils.get_windows_luabinaries_versions_text() + return table.concat(lua_utils.get_windows_luabinaries_versions(), ", ") +end + +function lua_utils.get_windows_luabinaries_package(lua_version) + if RUNTIME.osType ~= "windows" then + return nil + end + + local pkg_meta = windows_luabinaries_packages[lua_version] + if pkg_meta == nil then + return nil + end + + return { + archive_name = pkg_meta.archive_name, + executable_prefix = pkg_meta.executable_prefix, + wlua_prefix = pkg_meta.wlua_prefix, + dll_name = pkg_meta.dll_name, + url = "https://sourceforge.net/projects/luabinaries/files/" .. + pkg_meta.relative_path .. "/download?use_mirror=autoselect", + } +end + +function lua_utils.is_success_status(status) + return status == true or status == 0 +end + function lua_utils.is_dir(path) local status = os.execute("[ -d " .. path .. " ]") return status == 0