Skip to content
Open
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
18 changes: 17 additions & 1 deletion binstub_patch.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
unless ENV["APPBUNDLER_ALLOW_RVM"]
ENV["APPBUNDLER_ALLOW_RVM"] = "true"
ENV["GEM_PATH"] = [File.expand_path(File.join(__dir__, "..", "vendor")), ENV["GEM_PATH"]].compact.join(File::PATH_SEPARATOR)
end

# Prepend the package vendor dir, the current user's gem dir, and the Chef gem dir to
# GEM_PATH so that:
# 1. Knife's vendored dependencies are always found.
# 2. Knife plugins installed via `gem install knife-<plugin>` are discoverable on both
# Linux (~/.gem/ruby/VERSION) and Windows (%USERPROFILE%\.gem\ruby\VERSION).
# 3. Gems installed via `chef gem install` (~/.chef/gems) are immediately available.
#
# This block runs before appbundler's env_sanitizer (which calls Gem.clear_paths and
# re-reads GEM_PATH from ENV), so these additions are always picked up correctly.
knife_vendor = File.expand_path(File.join(__dir__, "..", "vendor"))
# chef-cli gem install places gems under ~/.chef/ruby/RUBY_API_VERSION/gems
# (e.g. ~/.chef/ruby/3.4.0/gems). RbConfig::CONFIG["ruby_version"] returns the same
# API version string that Ruby/Bundler use for the gem directory layout.
chef_gem_dir = File.join(Dir.home, ".chef", "ruby", RbConfig::CONFIG["ruby_version"], "gems")
existing_paths = ENV["GEM_PATH"]&.split(File::PATH_SEPARATOR) || []
ENV["GEM_PATH"] = ([knife_vendor, Gem.user_dir, chef_gem_dir] + existing_paths).uniq.join(File::PATH_SEPARATOR)
21 changes: 16 additions & 5 deletions habitat/aarch64-darwin/plan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ pkg_name=knife
pkg_origin=chef
ruby_pkg="core/ruby3_4"

# Ruby version for gem directory structure
# NOTE: Bundler normalizes Ruby versions to major.minor.0 format for gem compatibility.
ruby_gem_version="3.4.0"
# ruby_gem_version is set in do_before() once pkg_path_for is available.
# Declared here so it is in scope for all build functions.
ruby_gem_version=""

pkg_description="knife is a command-line tool that provides an interface between a local chef-repo and the Chef Infra Server."
pkg_deps=(${ruby_pkg} core/coreutils core/libarchive core/cacerts)
Expand Down Expand Up @@ -42,6 +42,11 @@ pkg_version() {

do_before() {
update_pkg_version
# Resolve the Ruby API version (e.g. 3.4.0) from the actual Habitat ruby binary.
# Must be done here rather than at plan top-level because pkg_path_for is not
# available until after package dependencies have been resolved.
ruby_gem_version="$($(pkg_path_for ${ruby_pkg})/bin/ruby -e 'print RbConfig::CONFIG["ruby_version"]')"
build_line "Resolved ruby_gem_version=${ruby_gem_version}"
}

do_unpack() {
Expand Down Expand Up @@ -137,8 +142,14 @@ do_install() {
set -e
# GEM_HOME points to the bundler-managed gem tree (where 'chef' and Gemfile deps live)
export GEM_HOME="$pkg_prefix/vendor/ruby/${ruby_gem_version}"
# GEM_PATH also includes the flat vendor tree (where gem install puts knife runtime deps)
export GEM_PATH="$pkg_prefix/vendor"
# GEM_PATH includes the flat vendor tree (knife runtime deps), the standard user gem dir
# (~/.gem/ruby/VERSION), and the Chef gem dir (~/.chef/gems) so that plugins installed
# via 'gem install knife-<plugin>' or 'chef gem install knife-<plugin>' are found at
# runtime without any additional configuration.
# vendor/ruby/VERSION — bundler-managed gem tree (train-core and all runtime deps)
# vendor — flat gem install tree (knife gem itself)
# ~/.gem/ruby/VERSION and ~/.chef/ruby/VERSION/gems — user-installed plugins
export GEM_PATH="$pkg_prefix/vendor:$pkg_prefix/vendor/ruby/${ruby_gem_version}:\${HOME}/.gem/ruby/${ruby_gem_version}:\${HOME}/.chef/ruby/${ruby_gem_version}/gems"
export DYLD_LIBRARY_PATH="$(pkg_path_for core/libarchive)/lib:\$DYLD_LIBRARY_PATH"
# SSL certificate verification - point OpenSSL to CA certificates
export SSL_CERT_FILE="$(pkg_path_for core/cacerts)/ssl/certs/cacert.pem"
Expand Down
12 changes: 12 additions & 0 deletions habitat/plan.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ function Invoke-Install {
Write-BuildLine "** generating binstubs for knife"
Invoke-Expression -Command "appbundler.bat $project_root $pkg_prefix/bin knife"
If ($lastexitcode -ne 0) { Exit $lastexitcode }

Write-BuildLine "** patching binstubs for direct execution and dynamic plugin loading"
$binstubPatch = Get-Content -Path "$project_root\binstub_patch.rb"
Get-ChildItem "$pkg_prefix\bin" | Where-Object { $_.Extension -notin @(".bat", ".ps1") } | ForEach-Object {
$lines = Get-Content -Path $_.FullName
$matchLine = $lines | Select-String -Pattern 'require "rubygems"' | Select-Object -First 1
if ($matchLine) {
$lineNum = $matchLine.LineNumber # 1-based; insert patch after this line
$newLines = $lines[0..($lineNum - 1)] + $binstubPatch + $lines[$lineNum..($lines.Count - 1)]
Set-Content -Path $_.FullName -Value $newLines
}
}
} finally {
Pop-Location
}
Expand Down
23 changes: 16 additions & 7 deletions habitat/plan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ export HAB_BLDR_CHANNEL="base-2025"
export HAB_REFRESH_CHANNEL="base-2025"
ruby_pkg="core/ruby3_4"

# Ruby version for gem directory structure
# NOTE: Bundler normalizes Ruby versions to major.minor.0 format for gem compatibility.
# Even if running Ruby 3.4.2, Bundler creates ruby/3.4.0 directory structure.
# This is standard behavior - gems built for 3.4.0 are compatible with 3.4.x patch versions.
ruby_gem_version="3.4.0"
# ruby_gem_version is set in do_before() once pkg_path_for is available.
# Declared here so it is in scope for all build functions.
ruby_gem_version=""

pkg_name="knife"
pkg_origin="chef"
Expand Down Expand Up @@ -36,6 +34,11 @@ pkg_version() {

do_before() {
update_pkg_version
# Resolve the Ruby API version (e.g. 3.4.0) from the actual Habitat ruby binary.
# Must be done here rather than at plan top-level because pkg_path_for is not
# available until after package dependencies have been resolved.
ruby_gem_version="$($(pkg_path_for ${ruby_pkg})/bin/ruby -e 'print RbConfig::CONFIG["ruby_version"]')"
build_line "Resolved ruby_gem_version=${ruby_gem_version}"
}

# Directories that contain executable binaries
Expand Down Expand Up @@ -143,8 +146,14 @@ do_install() {
set -e
# GEM_HOME points to the bundler-managed gem tree (where 'chef' and Gemfile deps live)
export GEM_HOME="$pkg_prefix/vendor/ruby/${ruby_gem_version}"
# GEM_PATH also includes the flat vendor tree (where gem install puts knife runtime deps)
export GEM_PATH="$pkg_prefix/vendor"
# GEM_PATH includes the flat vendor tree (knife runtime deps), the standard user gem dir
# (~/.gem/ruby/VERSION), and the Chef gem dir (~/.chef/gems) so that plugins installed
# via 'gem install knife-<plugin>' or 'chef gem install knife-<plugin>' are found at
# runtime without any additional configuration.
# vendor/ruby/VERSION — bundler-managed gem tree (train-core and all runtime deps)
# vendor — flat gem install tree (knife gem itself)
# ~/.gem/ruby/VERSION and ~/.chef/ruby/VERSION/gems — user-installed plugins
export GEM_PATH="$pkg_prefix/vendor:$pkg_prefix/vendor/ruby/${ruby_gem_version}:\${HOME}/.gem/ruby/${ruby_gem_version}:\${HOME}/.chef/ruby/${ruby_gem_version}/gems"
exec $(pkg_path_for ${ruby_pkg})/bin/ruby $pkg_prefix/libexec/knife "\$@"
EOF
chmod -v 755 "$pkg_prefix/bin/knife"
Expand Down
Loading