From 64af7b4ba19e3fee4ca429406ca8312a881ca6fc Mon Sep 17 00:00:00 2001 From: aooohan Date: Sat, 12 Sep 2026 15:44:07 +0800 Subject: [PATCH] fix: normalize macOS Java homes by executable layout Handle vendor-named bundles such as Liberica JDK 8 while preserving symlinks and hidden entries and refusing ambiguous or conflicting layouts. Fixes version-fox/vfox#657 --- .github/workflows/test.yml | 15 +++++++ hooks/post_install.lua | 78 ++++++++++++++++++++++++----------- tests/post_install_test.lua | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 tests/post_install_test.lua diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 063bc97..bf5a8f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,7 @@ name: Test Plugin on: + workflow_dispatch: pull_request: push: branches: [main] @@ -23,3 +24,17 @@ jobs: run: | go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/loongnix_test.lua + - name: Test macOS Java home normalization + run: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua + macos-layout: + runs-on: macos-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 actual macOS filesystem layouts + run: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua diff --git a/hooks/post_install.lua b/hooks/post_install.lua index fb87728..8ce3fba 100644 --- a/hooks/post_install.lua +++ b/hooks/post_install.lua @@ -1,32 +1,60 @@ -local strings = require("vfox.strings") +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end function PLUGIN:PostInstall(ctx) if RUNTIME.osType ~= "darwin" then return end - local sdkInfo = ctx.sdkInfo['java'] - local path = sdkInfo.path - local majorVersion = sdkInfo.note - local version = strings.split(sdkInfo.version, "-")[1] - local v = strings.split(version, "+")[1] - local needRemoveDir = { - ['/jdk-' .. v .. '.jdk'] = true, - ['/jdk-' .. majorVersion .. '.jdk'] = true, - ['/jdk-' .. version .. '.jdk'] = true, - ['/Contents/Home'] = true, - } - print("Checking if need to rename jdk files...") - for dir, _ in pairs(needRemoveDir) do - print("Checking path: " .. path .. dir) - if checkDir(path .. dir) then - print("Renaming jdk files: " .. path .. dir .. '/*') - if os.execute('mv ' .. path .. dir .. '/*' .. ' ' .. path) == 1 then - error('Failed to rename jdk files') - end - end + local path = ctx.sdkInfo.java.path + -- Archives may contain a flat JDK, Contents/Home, or a vendor-named + -- bundle. Identify the Java home by its executable, not its version name. + -- Move directory entries rather than copying so symlinks remain intact. + local script = [[ +root=$1 +if [ -x "$root/bin/java" ]; then + exit 0 +fi +home= +candidate() { + if [ -d "$1" ] && [ ! -L "$1" ] && [ -x "$1/bin/java" ]; then + if [ -n "$home" ] && [ "$home" != "$1" ]; then + echo "Multiple Java homes found in $root" >&2 + exit 1 + fi + home=$1 + fi +} +if [ ! -L "$root/Contents" ]; then + candidate "$root/Contents/Home" +fi +for bundle in "$root"/*; do + [ -d "$bundle" ] && [ ! -L "$bundle" ] || continue + candidate "$bundle" + if [ ! -L "$bundle/Contents" ]; then + candidate "$bundle/Contents/Home" + fi +done +if [ -z "$home" ]; then + echo "No Java home containing bin/java found in $root" >&2 + exit 1 +fi +# Check all destinations before moving anything, including hidden entries. +for entry in "$home"/* "$home"/.[!.]* "$home"/..?*; do + [ -e "$entry" ] || [ -L "$entry" ] || continue + target="$root/${entry##*/}" + if [ -e "$target" ] || [ -L "$target" ]; then + echo "Cannot normalize Java home: destination already exists: $target" >&2 + exit 1 + fi +done +for entry in "$home"/* "$home"/.[!.]* "$home"/..?*; do + [ -e "$entry" ] || [ -L "$entry" ] || continue + mv "$entry" "$root/" || exit 1 +done +]] + print("Checking Java home layout...") + if os.execute('sh -c ' .. shellQuote(script) .. ' sh ' .. shellQuote(path)) ~= 0 then + error('Failed to normalize Java home: ' .. path) end end - -function checkDir(path) - return os.execute('[ -d "' .. path .. '" ]') == 0 -end diff --git a/tests/post_install_test.lua b/tests/post_install_test.lua new file mode 100644 index 0000000..d48fc95 --- /dev/null +++ b/tests/post_install_test.lua @@ -0,0 +1,82 @@ +dofile('tests/support.lua') +PLUGIN = {} +RUNTIME = {osType = 'darwin'} +dofile('hooks/post_install.lua') + +local function quote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end +local function command(value) + assert(os.execute(value) == 0, value) +end +local function write(path, content) + local file = assert(io.open(path, 'w')) + assert(file:write(content)) + assert(file:close()) +end +local function read(path) + local file = io.open(path, 'r') + if not file then return nil end + local content = file:read('*a') + file:close() + return content +end +local base = os.tmpname() +os.remove(base) +base = base .. " JDK's & spaces" +command('mkdir -p ' .. quote(base)) +local count = 0 +local function fixture(layout) + count = count + 1 + local root = base .. '/' .. count + local home = root .. layout + command('mkdir -p ' .. quote(home .. '/bin') .. ' ' .. quote(home .. '/lib')) + write(home .. '/bin/java', '#!/bin/sh\nexit 0\n') + command('chmod +x ' .. quote(home .. '/bin/java')) + write(home .. '/lib/ct.sym', 'jdk symbols') + write(home .. '/.metadata', 'hidden metadata') + command('ln -s java ' .. quote(home .. '/bin/java-link')) + return root, home +end +local function install(root) + PLUGIN:PostInstall({sdkInfo = {java = { + path = root, version = '8.0.482+10-librca', note = '8' + }}}) +end +local ok, err = pcall(function() + -- The reported Liberica layout, plus existing bundle and flat layouts. + for _, layout in ipairs({ + '/jdk8u482-lite.jdk', '/jdk-8.jdk/Contents/Home', + '/jdk-21.0.2.jdk/Contents/Home', '/Contents/Home', '', + '/vendor-jdk/Contents/Home', + }) do + local root = fixture(layout) + install(root) + assert(read(root .. '/bin/java'), 'JAVA_HOME/bin/java missing for ' .. layout) + assert(read(root .. '/lib/ct.sym') == 'jdk symbols', layout) + assert(read(root .. '/.metadata') == 'hidden metadata', 'hidden file lost') + command('test -L ' .. quote(root .. '/bin/java-link')) + end + + -- Refuse ambiguous archives or collisions before moving anything. + local root, home = fixture('/jdk8u482-lite.jdk') + command('mkdir -p ' .. quote(root .. '/lib')) + write(root .. '/lib/keep', 'existing file') + assert(not pcall(install, root), 'must reject an existing destination') + assert(read(home .. '/bin/java') and read(root .. '/lib/keep') == 'existing file') + root, home = fixture('/one.jdk') + command('mkdir -p ' .. quote(root .. '/two.jdk/bin')) + write(root .. '/two.jdk/bin/java', '#!/bin/sh\nexit 0\n') + command('chmod +x ' .. quote(root .. '/two.jdk/bin/java')) + assert(not pcall(install, root), 'must reject multiple JAVA_HOME candidates') + assert(read(home .. '/bin/java') and read(root .. '/two.jdk/bin/java')) + + -- Other platforms retain their layout. + root, home = fixture('/jdk8u482-lite.jdk') + RUNTIME.osType = 'linux' + install(root) + assert(read(home .. '/bin/java') and not read(root .. '/bin/java')) +end) +command('rm -rf ' .. quote(base)) +assert(ok, err) +print('PASS: macOS JAVA_HOME layouts, metadata, links and collision checks')