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
25 changes: 25 additions & 0 deletions gems/kettle-jem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,12 +550,37 @@ files:
Rakefile:
strategy: merge
preference: destination # keep your custom tasks
add_template_only_nodes: true
certs/my.pem:
strategy: raw_copy # binary file, no merging
generated/report.md:
strategy: keep_destination # never touch this file
```

### Alef-generated Ruby packages

For an Alef-generated monorepo subgem, generate the package first, then hand
the Rakefile over to Kettle Jem by adding its repository-relative path to
`[workspace.ownership].user_owned` in `alef.toml`. Keep any existing entries:

```toml
[workspace.ownership]
user_owned = [
"packages/ruby/Rakefile",
"packages/ruby/structuredmerge_core.gemspec",
]
```

Use the Rakefile override above in the package's Kettle Jem configuration.
The monorepo subgem profile seeds `accept_template` for a new configuration;
an explicit `merge` strategy survives subsequent profile synchronization.
Destination preference retains native build tasks and `rb_sys` configuration,
while template-only nodes add Kettle's tasks. Once handed off, Alef no longer
regenerates or verifies that file. Run `alef verify --exit-code` after the
handoff, and rerun Alef generation and Kettle templating to check convergence.
Configure the package as a member of `kettle-family` for its local-template
and release workflows.

### Environment Variables & CLI Options

Kettle::Jem behavior is controlled via environment variables (which double as
Expand Down
8 changes: 7 additions & 1 deletion gems/kettle-jem/lib/kettle/jem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3433,7 +3433,13 @@ def recipe_pack(facts)
facts: %w[templates shim]
)
end
unless shim_template_profile?(facts)
# Cleanup plans against the original file and would overwrite a merged
# Rakefile when both recipes change it. The template merge owns that
# file whenever it is selected; retain cleanup for standalone runs.
rakefile_template = recipes.any? do |recipe|
TEMPLATE_SOURCE_APPLICATION_RECIPE.match?(recipe.fetch(:name)) && recipe.fetch(:target_path) == "Rakefile"
end
unless shim_template_profile?(facts) || rakefile_template
recipes << recipe_entry(
"rakefile_scaffold_cleanup",
"Rakefile",
Expand Down
95 changes: 95 additions & 0 deletions gems/kettle-jem/spec/fixtures/alef/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# This file is auto-generated by alef — DO NOT EDIT.
# alef:hash:53a4b3692134151096d1676cc69dbb7778ef8df86acdf81b887282dc08d085b2
# To regenerate: alef generate
# To verify freshness: alef verify

# frozen_string_literal: true

require "bundler"
Bundler::GemHelper.install_tasks name: "structuredmerge_core"
require "rb_sys/extensiontask"
require "rspec/core/rake_task"

# Absolute path to the gem package directory, used as the anchor for resolving
# the gemspec and the native extension's Cargo manifest.
GEM_ROOT = __dir__
# Loaded gemspec used by Rake::ExtensionTask to compile the native extension.
GEMSPEC = Gem::Specification.load(File.expand_path("structuredmerge_core.gemspec", GEM_ROOT))

# Set of supported platform identifiers for native gem cross-compilation.
# Used by `rb_sys/extensiontask` to drive the `rake compile:<platform>` tasks
# that produce platform-specific prebuilt gems published alongside the source
# gem on RubyGems.
CROSS_PLATFORMS = %w[
x86_64-linux
aarch64-linux
arm64-darwin
x86_64-darwin
x64-mingw-ucrt
].freeze

# rb_sys 0.9.x's Cargo::Metadata runs `cargo metadata` without `--manifest-path`,
# so it resolves to whatever workspace contains cwd. In this monorepo the root
# workspace excludes our crate, so the lookup fails with PackageNotFoundError.
# Chdir-around-construction also doesn't work because Rake::ExtensionTask resolves
# its own paths (lib_dir, ext_dir, task wiring) at construction time relative to
# cwd, breaking the compile pipeline. Patch Cargo::Metadata#cargo_metadata to add
# the explicit `--manifest-path` pointing at the crate's Cargo.toml so the lookup
# is unambiguous regardless of cwd.
MANIFEST_PATH = File.expand_path("ext/structuredmerge_core_rb/native/Cargo.toml", GEM_ROOT)

# @!visibility private
module RbSys
# @!visibility private
module Cargo
# @!visibility private
class Metadata
manifest_path = MANIFEST_PATH
define_method(:cargo_metadata) do
return @cargo_metadata if @cargo_metadata

cargo = ENV["CARGO"] || "cargo"
args = ["metadata", "--format-version", "1", "--manifest-path", manifest_path]
args << "--no-deps" unless @deps
out, stderr, status = Open3.capture3(cargo, *args)
out.force_encoding(Encoding::UTF_8)
raise "exited with non-zero status (#{status})" unless status.success?

data = JSON.parse(out)
raise "metadata must be a Hash" unless data.is_a?(Hash)

@cargo_metadata = data
rescue StandardError => e
raise CargoMetadataError.new(e, stderr)
end
private :cargo_metadata
end
end
end

RbSys::ExtensionTask.new("structuredmerge-core-rb", GEMSPEC) do |ext|
ext.lib_dir = "lib"
ext.ext_dir = "ext/structuredmerge_core_rb/native"
ext.source_pattern = "*.{}"
ext.platform = "ruby"
ext.cross_compile = true
ext.cross_platform = CROSS_PLATFORMS
# Pin cross_compile_versions to Ruby 3.2-3.5 stable releases.
# This overrides the container's RUBY_CC_VERSION env var at rake task definition time.
# The setter was added in a later rb_sys version; guard against older gem installations
# where the method does not exist (older rb_sys lines predating the 0.9.130 floor).
# rb-sys-dock 0.9.x ships images for Ruby 3.2, 3.3, 3.4, and 3.5; this list must
# match those available images. Per-ABI platform gem windows are controlled by rake-compiler-dock.
ext.cross_compile_versions = %w[3.5.0 3.4.9 3.3.11 3.2.11] if ext.respond_to?(:cross_compile_versions=)
end

RSpec::Core::RakeTask.new(:spec)

# rake-compiler's `compile` task is a no-op when cross_compile is true; the real
# work hangs off `compile:<ruby_platform>`. Wire `compile` → `compile:ruby` so
# both the dev shorthand and CI's
# `BUNDLE_PATH=vendor/bundle ruby -S bundle exec ruby -S rake compile` actually build.
task compile: "compile:ruby"

task spec: :compile
task default: :spec
32 changes: 32 additions & 0 deletions gems/kettle-jem/spec/integration/structural_merge_template_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2334,6 +2334,38 @@ def call
end
end

it "converges when overlaying the packaged Rakefile on an Alef native package" do
Dir.mktmpdir("kettle-jem-alef-rakefile") do |root|
write_tree(root, {
"structuredmerge_core.gemspec" => 'Gem::Specification.new { |spec| spec.name = "structuredmerge-core" }',
".kettle-jem.yml" => <<~YAML,
templates:
root: template
apply: true
entries:
- Rakefile
files:
Rakefile:
strategy: merge
preference: destination
add_template_only_nodes: true
YAML
"Rakefile" => File.read(File.expand_path("../fixtures/alef/Rakefile", __dir__)),
"template/Rakefile.example" => File.read(File.expand_path("../../lib/kettle/jem/templates/Rakefile.example", __dir__))
})

described_class.apply_project(root, env: {}, run_options: {skip_commit: true})
first = File.read(File.join(root, "Rakefile"))
described_class.apply_project(root, env: {}, run_options: {skip_commit: true})
second = File.read(File.join(root, "Rakefile"))

expect(second).to eq(first)
expect(second).to include('require "rb_sys/extensiontask"', 'ext.cross_compile = true', 'ext.cross_platform = CROSS_PLATFORMS')
expect(second).to include('task compile: "compile:ruby"', 'require "kettle/dev"')
expect(second.scan("# This file is auto-generated by alef").length).to eq(1)
end
end

it "normalizes generated Rakefile section spacing after merge" do
rakefile = described_class.send(:normalize_generated_rakefile, <<~RAKE)
task :custom do
Expand Down
Loading