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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ jobs:
cat > ${{ env.BUNDLE_GEMFILE }} << 'EOF'
source 'https://gem.coop'
gem 'rake'
gem 'bigdecimal', '4.1.3'
EOF

- name: Run setup-ruby-flash with bundler-cache
Expand All @@ -155,6 +156,8 @@ jobs:
bundle check --gemfile "$BUNDLE_GEMFILE"
echo "==> Smoke a Rake:"
bundle exec rake -T
echo "==> Test native extension:"
bundle exec ruby -rbigdecimal -e 'puts BigDecimal("1.23")'

# Test the action with ore gem installation (Compiled Extensions)
test-action-with-ore-compiled:
Expand Down
212 changes: 210 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,12 @@ runs:
fi
fi

# On Windows, fix clean_install.rs to use forward slashes in make sitearchdir/sitelibdir arguments
# so make's /bin/sh recipe execution does not unescape backslashes.
if [ "$RUNNER_OS" = "Windows" ] && [ -f crates/rv/src/commands/clean_install.rs ]; then
sed -i 's/tmp_dir\.path()/tmp_dir.path().as_str().replace(\x27\\\\\x27, "\/")/g' crates/rv/src/commands/clean_install.rs || true
fi

# Build rv using cargo
cargo build --release -p rv

Expand Down Expand Up @@ -950,7 +956,169 @@ runs:
echo "GEM_HOME=$ACTUAL_GEM_HOME_WIN" >> "$GITHUB_ENV"
echo "GEM_PATH=$ACTUAL_GEM_PATH_WIN" >> "$GITHUB_ENV"
[ -n "$RUBY_ROOT" ] && echo "RUBY_ROOT=$(cygpath -m "$RUBY_ROOT")" >> "$GITHUB_ENV"

# Enable MSYS2 / MinGW DevKit for compiling native C extension gems
RIDK_CMD="$RUBY_BIN_DIR/ridk.cmd"
if [ -f "$RIDK_CMD" ]; then
echo "Enabling MSYS2 DevKit via ridk..."
RIDK_OUTPUT=$(cmd.exe /c "set MAKE=make && \"$(cygpath -w "$RIDK_CMD")\" enable" 2>/dev/null || true)
while IFS='=' read -r key val; do
val=$(echo "$val" | tr -d '\r')
key=$(echo "$key" | tr -d '\r')
case "$key" in
PATH)
IFS=';' read -ra ADDR <<< "$val"
for p in "${ADDR[@]}"; do
if [ -n "$p" ] && [ -d "$p" ]; then
echo "$p" >> "$GITHUB_PATH"
PATH="$p:$PATH"
fi
done
;;
MSYSTEM|MINGW_PACKAGE_PREFIX|RI_DEVKIT|PKG_CONFIG_PATH|ACLOCAL_PATH|MANPATH)
echo "$key=$val" >> "$GITHUB_ENV"
export "$key=$val"
;;
esac
done <<< "$RIDK_OUTPUT"
echo "MAKE=make" >> "$GITHUB_ENV"
export MAKE=make
elif [ -d "C:/msys64" ]; then
echo "Enabling pre-installed MSYS2 DevKit..."
MAJOR_VERSION=$(echo "$FULL_RUBY_VERSION" | cut -d. -f1)
MINOR_VERSION=$(echo "$FULL_RUBY_VERSION" | cut -d. -f2)
if [ "$MAJOR_VERSION" -ge 4 ] || ([ "$MAJOR_VERSION" -eq 3 ] && [ "$MINOR_VERSION" -ge 1 ]); then
TOOLCHAIN="ucrt64"
else
TOOLCHAIN="mingw64"
fi
if [ -d "C:/msys64/$TOOLCHAIN/bin" ]; then
echo "C:\\msys64\\$TOOLCHAIN\\bin" >> "$GITHUB_PATH"
PATH="/c/msys64/$TOOLCHAIN/bin:$PATH"
fi
if [ -d "C:/msys64/usr/bin" ]; then
echo "C:\\msys64\\usr\\bin" >> "$GITHUB_PATH"
PATH="/c/msys64/usr/bin:$PATH"
fi
echo "MAKE=make" >> "$GITHUB_ENV"
export MAKE=make
echo "RI_DEVKIT=C:\\msys64" >> "$GITHUB_ENV"
export RI_DEVKIT="C:\\msys64"
echo "MSYSTEM=${TOOLCHAIN^^}" >> "$GITHUB_ENV"
export MSYSTEM="${TOOLCHAIN^^}"
fi

# Prepend Ruby bin dir to GITHUB_PATH so Ruby binaries take precedence
echo "$(cygpath -w "$RUBY_BIN_DIR")" >> "$GITHUB_PATH"

# Ensure MSYS2 environments inherit the PATH from Windows
echo "MSYS2_PATH_TYPE=inherit" >> "$GITHUB_ENV"
export MSYS2_PATH_TYPE=inherit

# Ensure MSYS2 and make can resolve Windows drive paths formatted as /C/... or /D/...
# by adding /etc/fstab mount entries, directory junctions, and MSYS2 symlinks.
MSYS_ROOTS=()
[ -d "/c/msys64" ] && MSYS_ROOTS+=("C:/msys64")
[ -d "C:/msys64" ] && MSYS_ROOTS+=("C:/msys64")
if [ -n "$RI_DEVKIT" ]; then
DEVKIT_POSIX="$(cygpath -m "$RI_DEVKIT" 2>/dev/null || echo "$RI_DEVKIT" | tr '\\' '/')"
[ -d "$DEVKIT_POSIX" ] && MSYS_ROOTS+=("$DEVKIT_POSIX")
fi
GIT_ROOT_POSIX="$(cygpath -m / 2>/dev/null || true)"
[ -n "$GIT_ROOT_POSIX" ] && [ -d "$GIT_ROOT_POSIX" ] && MSYS_ROOTS+=("$GIT_ROOT_POSIX")
MAKE_BIN=$(command -v make 2>/dev/null || which make 2>/dev/null || true)
if [ -n "$MAKE_BIN" ]; then
MAKE_ROOT="$(dirname "$(dirname "$(cygpath -m "$MAKE_BIN" 2>/dev/null || echo "$MAKE_BIN" | tr '\\' '/')")")"
[ -d "$MAKE_ROOT" ] && MSYS_ROOTS+=("$MAKE_ROOT")
fi

ACTIVE_DRIVES=(C D)
for d in E F G H; do
d_lower=$(echo "$d" | tr '[:upper:]' '[:lower:]')
if [ -d "/$d_lower" ] || [ -d "${d}:/" ]; then
ACTIVE_DRIVES+=("$d")
fi
done

UNIQUE_MSYS_ROOTS=()
for r in "${MSYS_ROOTS[@]}"; do
skip=""
for u in "${UNIQUE_MSYS_ROOTS[@]}"; do
[ "$r" = "$u" ] && skip=1 && break
done
[ -z "$skip" ] && UNIQUE_MSYS_ROOTS+=("$r")
done

echo "==> Configuring MSYS2 drive mappings for roots: ${UNIQUE_MSYS_ROOTS[*]} and drives: ${ACTIVE_DRIVES[*]}"
for root in "${UNIQUE_MSYS_ROOTS[@]}"; do
mkdir -p "$root/etc"
FSTAB="$root/etc/fstab"
touch "$FSTAB"
for d in "${ACTIVE_DRIVES[@]}"; do
grep -E -q "^${d}:/[[:space:]]+/${d}([[:space:]]|$)" "$FSTAB" 2>/dev/null || echo "${d}:/ /${d} ntfs binary,noacl,auto 1 1" >> "$FSTAB"
done
echo "Fstab ($FSTAB):"
cat "$FSTAB"
done

# Create directory junctions using PowerShell and cmd mklink /J
powershell -NoProfile -Command "
foreach (\$d in @('C', 'D')) {
\$target = \"\${d}:\\\"
foreach (\$root in @('C:\\msys64', 'C:\\Program Files\\Git', 'C:\\mingw64', 'C:', 'D:')) {
if (Test-Path -LiteralPath \$root) {
\$junction = Join-Path \$root \$d
if (Test-Path -LiteralPath \$junction) {
\$item = Get-Item -LiteralPath \$junction -Force
if (\$item.LinkType -ne 'Junction') {
Remove-Item -LiteralPath \$junction -Force -Recurse
}
}
if (-not (Test-Path -LiteralPath \$junction)) {
New-Item -ItemType Junction -Path \$junction -Value \$target -Force -ErrorAction SilentlyContinue | Out-Null
}
}
}
}
" 2>&1 || true

# Fallback mklink /J for cmd compatibility
for d1 in "${ACTIVE_DRIVES[@]}"; do
for d2 in "${ACTIVE_DRIVES[@]}"; do
cmd.exe /c "if not exist ${d1}:\\${d2} mklink /J ${d1}:\\${d2} ${d2}:\\. " 2>&1 || true
done
done

echo "==> Verifying /C and /D paths in Git Bash:"
ls -ld /C 2>&1 || echo "ls /C failed"
ls -ld /D 2>&1 || echo "ls /D failed"
if [ -d "$RUBY_PREFIX" ]; then
RUBY_PREFIX_UPPER="$(echo "$RUBY_PREFIX" | sed -E 's/^\/([a-z])\//\/\U\1\//')"
echo "Checking Ruby header in uppercase path: $RUBY_PREFIX_UPPER/include/ruby-${MAJOR_MINOR}.0/ruby.h"
if [ -f "$RUBY_PREFIX_UPPER/include/ruby-${MAJOR_MINOR}.0/ruby.h" ]; then
echo "SUCCESS: Ruby header is accessible via uppercase drive path!"
else
echo "WARNING: Ruby header not found at $RUBY_PREFIX_UPPER/include/ruby-${MAJOR_MINOR}.0/ruby.h"
ls -la "$RUBY_PREFIX/include" 2>&1 || true
fi
fi

echo "==> Verifying Win32 junction resolution via PowerShell:"
powershell -NoProfile -Command "
Get-Item 'C:\C', 'C:\D', 'D:\C', 'D:\D', 'C:\msys64\C', 'C:\msys64\D' -ErrorAction SilentlyContinue | Select-Object FullName, LinkType, Target | Format-Table -AutoSize
\$testHeaderD = 'D:\C\Users\runneradmin\AppData\Roaming\rv\rubies\ruby-' + '${FULL_RUBY_VERSION}' + '\include\ruby-' + '${MAJOR_MINOR}' + '.0\ruby.h'
\$testHeaderMsys = 'C:\msys64\C\Users\runneradmin\AppData\Roaming\rv\rubies\ruby-' + '${FULL_RUBY_VERSION}' + '\include\ruby-' + '${MAJOR_MINOR}' + '.0\ruby.h'
if (Test-Path \$testHeaderD) {
Write-Host 'SUCCESS: Header is accessible via Win32 D:\C path:' \$testHeaderD
} else {
Write-Host 'WARNING: Header not found via Win32 D:\C path:' \$testHeaderD
}
if (Test-Path \$testHeaderMsys) {
Write-Host 'SUCCESS: Header is accessible via MSYS2 C:\msys64\C path:' \$testHeaderMsys
} else {
Write-Host 'WARNING: Header not found via MSYS2 C:\msys64\C path:' \$testHeaderMsys
}
" 2>&1 || true
else
echo "GEM_HOME=$ACTUAL_GEM_HOME" >> "$GITHUB_ENV"
echo "GEM_PATH=$ACTUAL_GEM_PATH" >> "$GITHUB_ENV"
Expand Down Expand Up @@ -1309,7 +1477,47 @@ runs:
if [ "${{ steps.cache-bundler-gems.outputs.cache-hit }}" != "true" ]; then
RV_CI_ARGS+=(--force)
fi
setup_ruby_flash_retry "rv clean-install" "$MAX_RETRIES" "$RV_BIN" ci "${RV_CI_ARGS[@]}"
GEM_MANAGER="rv clean-install"
if ! setup_ruby_flash_retry "rv clean-install" "$MAX_RETRIES" "$RV_BIN" ci "${RV_CI_ARGS[@]}"; then
echo "::warning::rv clean-install failed; falling back to bundle install"
GEM_MANAGER="bundle install (rv clean-install fallback)"
bundle config set --local path vendor/bundle
setup_ruby_flash_retry "bundle install for $GEMFILE" "$MAX_RETRIES" bundle install --gemfile "$GEMFILE" --jobs 4
fi

# On Windows, fix up native extensions if rv clean-install was affected by
# the make /bin/sh backslash unescaping issue.
if [ "$RUNNER_OS" = "Windows" ] && [ -d "$BUNDLE_PATH_VALUE" ]; then
for gem_dir in "$BUNDLE_PATH_VALUE"/ruby/*/gems/*; do
[ -d "$gem_dir" ] || continue
gem_name_ver="$(basename "$gem_dir")"
lib_dir="$gem_dir/lib"
ext_src="$gem_dir/ext"
[ -d "$ext_src" ] || continue

while IFS= read -r so_file; do
[ -f "$so_file" ] || continue
so_name="$(basename "$so_file")"
mkdir -p "$lib_dir"
if [ ! -f "$lib_dir/$so_name" ]; then
echo "Recovered native extension $so_name -> $lib_dir/$so_name"
cp -f "$so_file" "$lib_dir/$so_name"
fi
for ext_api_dir in "$BUNDLE_PATH_VALUE"/ruby/*/extensions/*/*; do
[ -d "$ext_api_dir" ] || continue
target_ext_dir="$ext_api_dir/$gem_name_ver"
mkdir -p "$target_ext_dir"
if [ ! -f "$target_ext_dir/$so_name" ]; then
echo "Recovered native extension $so_name -> $target_ext_dir/$so_name"
cp -f "$so_file" "$target_ext_dir/$so_name"
fi
done
done < <(find "$ext_src" -type f \( -name "*.so" -o -name "*.dll" \) 2>/dev/null)

# Clean up any mangled directory created by make install (e.g., ext/**/D:a* or *:*)
find "$ext_src" -mindepth 1 -maxdepth 3 -type d -name "*:*" -exec rm -rf {} + 2>/dev/null || true
done
fi

END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))
Expand Down Expand Up @@ -1337,7 +1545,7 @@ runs:
echo "| RubyGems Version | $RUBYGEMS_VERSION |" >> $GITHUB_STEP_SUMMARY
echo "| Bundler Version | $BUNDLER_VERSION |" >> $GITHUB_STEP_SUMMARY
echo "| rv Version | ${{ steps.rv-version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
echo "| Gem Manager | rv clean-install |" >> $GITHUB_STEP_SUMMARY
echo "| Gem Manager | $GEM_MANAGER |" >> $GITHUB_STEP_SUMMARY
echo "| Platform | ${{ steps.platform.outputs.platform }} |" >> $GITHUB_STEP_SUMMARY
echo "| Gem Cache Hit | ${{ steps.cache-bundler-gems.outputs.cache-hit }} |" >> $GITHUB_STEP_SUMMARY
echo "| Gem Install Time | ${ELAPSED}s |" >> $GITHUB_STEP_SUMMARY
Expand Down
2 changes: 1 addition & 1 deletion scripts/retry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# "Could not find gem ..." is deliberately absent: a just-published gem can be
# missing from the index for a few minutes, and retrying is the fix.
SETUP_RUBY_FLASH_DETERMINISTIC_FAILURES='native extension build failure => Gem::Ext::BuildError|Failed to build gem native extension
SETUP_RUBY_FLASH_DETERMINISTIC_FAILURES='native extension build failure => Gem::Ext::BuildError|Failed to build gem native extension|Could not compile gem .* extension|CiError\(CompileFailures
dependency resolution conflict => could not find compatible versions|version solving has failed
incompatible Ruby version => requires ruby version|conflicting requirements for the ruby version
Gemfile or gemspec error => there was an error parsing .?gemfile|gemfile syntax error|there was an error while loading .*\.gemspec
Expand Down
5 changes: 5 additions & 0 deletions spec/action_yml_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
expect(install_step.fetch('if')).to eq("steps.setup-plan.outputs.fast-bundler-install == 'true'")
expect(install_step.fetch('run')).to include('RV_CI_ARGS=(--gemfile "$GEMFILE")')
expect(install_step.fetch('run')).to include('"$RV_BIN" ci "${RV_CI_ARGS[@]}"')
expect(install_step.fetch('run')).to include('rv clean-install failed; falling back to bundle install')
expect(install_step.fetch('run')).to include('bundle install --gemfile "$GEMFILE" --jobs 4')
end

it 'retries dependency resolution and rv gem installation without changing sources' do
Expand Down Expand Up @@ -241,5 +243,8 @@
setup_script = setup_step.fetch('run')
expect(setup_script).to include('cygpath -m "$ACTUAL_GEM_HOME"')
expect(setup_script).to include('cygpath -w "$RUBY_BIN_DIR"')
expect(setup_script).to include('RIDK_CMD="$RUBY_BIN_DIR/ridk.cmd"')
expect(setup_script).to include('Enabling MSYS2 DevKit via ridk...')
expect(setup_script).to include('mklink /J')
end
end
3 changes: 3 additions & 0 deletions spec/retry_sh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ def classify(line)

it 'recognizes each deterministic failure category' do
expect(classify('Gem::Ext::BuildError: ERROR')).to eq('native extension build failure')
warning = "Warning: Could not compile gem racc-1.8.1's extension ext/racc/cparse/extconf.rb. Got exit code 2."
expect(classify(warning)).to eq('native extension build failure')
expect(classify('Error: CiError(CompileFailures { gem: "racc-1.8.1" })')).to eq('native extension build failure')
expect(classify('version solving has failed.')).to eq('dependency resolution conflict')
expect(classify('nokogiri-1.18.0 requires ruby version >= 3.2')).to eq('incompatible Ruby version')
expect(classify('[!] There was an error parsing `Gemfile`: syntax error')).to eq('Gemfile or gemspec error')
Expand Down
Loading