Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use bundle install --target-rbconfig to install gems for Wasm target #490

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ jobs:
if: ${{ inputs.prerel_name != '' && matrix.entry.prerelease != '' }}
- name: rake ${{ matrix.entry.task }}
run: ./build-exec rake --verbose ${{ matrix.entry.task }}
- uses: actions/upload-artifact@v4
if: ${{ matrix.entry.artifact }}
with:
name: ${{ matrix.entry.artifact_name }}
path: ${{ matrix.entry.artifact }}
- uses: ruby/setup-ruby@v1
if: ${{ matrix.entry.test != '' }}
with:
Expand All @@ -152,11 +157,6 @@ jobs:
bundle install --with=check --without=development
rake ${{ matrix.entry.test }}
if: ${{ matrix.entry.test != '' }}
- uses: actions/upload-artifact@v4
if: ${{ matrix.entry.artifact }}
with:
name: ${{ matrix.entry.artifact_name }}
path: ${{ matrix.entry.artifact }}

release-artifacts:
needs: [rake-tasks]
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ $ gh run download <run-id>
$ for pkg in cross-gem/pkg/ruby_wasm-*; do gem push $pkg; done
$ gem build && gem push ruby_wasm-*.gem && rm ruby_wasm-*.gem
$ (cd packages/gems/js/ && gem build && gem push js-*.gem && rm js-*.gem)
$ rake bump_dev_version
```

## Release Channels
Expand Down
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ NPM_PACKAGES = [
{
name: "ruby-head-wasm-wasi",
ruby_version: "head",
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
gemfile: "packages/npm-packages/ruby-head-wasm-wasi/Gemfile",
target: "wasm32-unknown-wasip1",
enable_component_model: true,
},
{
name: "ruby-3.3-wasm-wasi",
ruby_version: "3.3",
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
gemfile: "packages/npm-packages/ruby-3.3-wasm-wasi/Gemfile",
target: "wasm32-unknown-wasip1"
},
{
name: "ruby-3.2-wasm-wasi",
ruby_version: "3.2",
gemfile: "packages/npm-packages/ruby-wasm-wasi/Gemfile",
gemfile: "packages/npm-packages/ruby-3.2-wasm-wasi/Gemfile",
target: "wasm32-unknown-wasip1"
},
{ name: "ruby-wasm-wasi", target: "wasm32-unknown-wasip1" }
Expand Down
23 changes: 22 additions & 1 deletion bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,28 @@ set -vx
root="$(cd "$(dirname "$0")/.." && pwd)"

env BUNDLE_GEMFILE="$root/Gemfile" bundle install
env BUNDLE_GEMFILE="$root/packages/npm-packages/ruby-wasm-wasi/Gemfile" bundle install
for gemfile in $root/packages/npm-packages/*/Gemfile; do
# Skip ruby-head-wasm-wasi's Gemfile because it does not need to be installed here
if [[ "$gemfile" == *"/ruby-head-wasm-wasi/Gemfile" ]]; then
continue
fi

# FIXME: This is a workaround for the following issue:
# 1. `bundle install` does not support auto-self-upgrade for pre-release bundler versions suffixed with ".dev"
# 2. ruby-head-wasm-wasi's component build depends on 2.6.0.dev, which added --target-rbconfig support
# 3. If the "bundle" command here is earlier than 2.6.0.dev, "bundle install" does *not* self-upgrade to
# the specified pre-release version, and it overwrites the Gemfile.lock with the earlier command version.
# 4. Overwritten Gemfile.lock with the earlier bundler version causes auto-self-downgrade when running
# "bundle install --target-rbconfig" inside "rbwasm build" command, then it fails because the earlier
# bundler version does not support --target-rbconfig.
#
# Thus, we temporarily discard the Gemfile.lock changes here to prevent the auto-self-downgrade issue.
# This workaround should be removed once we drop static Ruby builds from ruby-head-wasm-wasi or
# a stable bundler version is released with --target-rbconfig support.
cp "$gemfile.lock" "$gemfile.lock.orig"
env BUNDLE_GEMFILE="$gemfile" bundle install
mv "$gemfile.lock.orig" "$gemfile.lock"
done

# Build vendored jco if Rust toolchain is available and submodule is checked out
if command -v rustc && [ -f vendor/jco/package.json ]; then
Expand Down
3 changes: 3 additions & 0 deletions ext/ruby_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ impl WasiVirt {
// Disable sockets for now since `sockets/ip-name-lookup` is not
// supported by @bytecodealliance/preview2-shim yet
virt.sockets(false);
// Disable http for now since `http` is not supported by
// wasmtime yet
virt.http(false);
Ok(())
})
}
Expand Down
10 changes: 7 additions & 3 deletions lib/ruby_wasm/build/product/crossruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ def do_extconf(executor, crossruby)
return
end
objdir = product_build_dir crossruby
rbconfig_rb = Dir.glob(File.join(crossruby.dest_dir, "usr/local/lib/ruby/*/wasm32-wasi/rbconfig.rb")).first
rbconfig_rb = crossruby.rbconfig_rb
raise "rbconfig.rb not found" unless rbconfig_rb
extconf_args = [
"-C", objdir,
"#{@srcdir}/extconf.rb",
"--target-rbconfig=#{rbconfig_rb}",
]
extconf_args << "--enable-component-model" if @features.support_component_model?
extconf_args << "--disable-component-model" unless @features.support_component_model?
executor.system crossruby.baseruby_path, *extconf_args
end

Expand Down Expand Up @@ -111,7 +111,7 @@ def do_legacy_extconf(executor, crossruby)
"-I#{crossruby.build_dir}",
"--",
]
extconf_args << "--enable-component-model" if @features.support_component_model?
extconf_args << "--disable-component-model" unless @features.support_component_model?
# Clear RUBYOPT to avoid loading unrelated bundle setup
executor.system crossruby.baseruby_path,
*extconf_args,
Expand Down Expand Up @@ -301,6 +301,10 @@ def extinit_c_erb
File.expand_path("../crossruby/extinit.c.erb", __FILE__)
end

def rbconfig_rb
Dir.glob(File.join(dest_dir, "usr/local/lib/ruby/*/wasm32-wasi/rbconfig.rb")).first
end

def baseruby_path
File.join(@baseruby.install_dir, "bin/ruby")
end
Expand Down
9 changes: 6 additions & 3 deletions lib/ruby_wasm/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,10 @@ def self.bundled_patches_path

def derive_packager(options)
__skip__ = definition = nil
__skip__ = if defined?(Bundler) && !options[:disable_gems]
features = RubyWasm::FeatureSet.derive_from_env
# The head ruby & dynamic linking uses "bundle" command to build gems instead of in-process integration.
use_in_process_gem_building = !(options[:ruby_version] == "head" && features.support_dynamic_linking?)
__skip__ = if defined?(Bundler) && !options[:disable_gems] && use_in_process_gem_building
begin
# Silence Bundler UI if --print-ruby-cache-key is specified not to bother the JSON output.
level = options[:print_ruby_cache_key] ? :silent : Bundler.ui.level
Expand All @@ -321,10 +324,10 @@ def derive_packager(options)
Bundler.ui.level = old_level
end
end
RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles}" if definition
RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles.map(&:to_s).join(", ")}" if definition
RubyWasm::Packager.new(
root, build_config(options), definition,
features: RubyWasm::FeatureSet.derive_from_env
features: features,
)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_wasm/packager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def package(executor, dest_dir, options)

ruby_core.build_gem_exts(executor, fs.bundle_dir)

fs.package_gems
fs.package_gems unless features.support_component_model?
fs.remove_non_runtime_files(executor)
if options[:stdlib]
options[:without_stdlib_components].each do |component|
Expand Down
85 changes: 50 additions & 35 deletions lib/ruby_wasm/packager/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def build(executor, options)
def build_strategy
@build_strategy ||=
begin
has_exts = @packager.specs.any? { |spec| spec.extensions.any? }
if @packager.features.support_dynamic_linking?
DynamicLinking.new(@packager)
else
Expand Down Expand Up @@ -59,14 +58,6 @@ def specs_with_extensions
end
end

def wasi_exec_model
# TODO: Detect WASI exec-model from binary exports (_start or _initialize)
use_js_gem = @packager.specs.any? do |spec|
spec.name == "js"
end
use_js_gem ? "reactor" : "command"
end

def with_unbundled_env(&block)
__skip__ = if defined?(Bundler)
Bundler.with_unbundled_env(&block)
Expand Down Expand Up @@ -138,12 +129,16 @@ def _link_gem_exts(executor, build, ruby_root, gem_home, module_bytes)
wasi_sdk_path = toolchain.wasi_sdk_path
libraries << File.join(wasi_sdk_path, "share/wasi-sysroot/lib/wasm32-wasi", lib)
end
wasi_adapter = RubyWasm::Packager::ComponentAdapter.wasi_snapshot_preview1(wasi_exec_model)
adapters = [wasi_adapter]
dl_openable_libs = []
dl_openable_libs << [File.dirname(ruby_root), Dir.glob(File.join(ruby_root, "lib", "ruby", "**", "*.so"))]
dl_openable_libs << [gem_home, Dir.glob(File.join(gem_home, "**", "*.so"))]

has_js_so = dl_openable_libs.any? do |root, libs|
libs.any? { |lib| lib.end_with?("/js.so") }
end
wasi_adapter = RubyWasm::Packager::ComponentAdapter.wasi_snapshot_preview1(has_js_so ? "reactor" : "command")
adapters = [wasi_adapter]

linker = RubyWasmExt::ComponentLink.new
linker.use_built_in_libdl(true)
linker.stub_missing_functions(false)
Expand Down Expand Up @@ -187,31 +182,43 @@ def _build_gem_exts(executor, build, gem_home)
baseruby.build(executor)
end

exts = specs_with_extensions.flat_map do |spec, exts|
exts.map do |ext|
ext_feature = File.dirname(ext) # e.g. "ext/cgi/escape"
ext_srcdir = File.join(spec.full_gem_path, ext_feature)
ext_relative_path = File.join(spec.full_name, ext_feature)
prod = RubyWasm::CrossRubyExtProduct.new(
ext_srcdir,
build.toolchain,
features: @packager.features,
ext_relative_path: ext_relative_path
)
[prod, spec]
end
end
crossruby = build.crossruby
rbconfig_rb = crossruby.rbconfig_rb

exts.each do |prod, spec|
libdir = File.join(gem_home, "gems", spec.full_name, spec.raw_require_paths.first)
extra_mkargs = [
"sitearchdir=#{libdir}",
"sitelibdir=#{libdir}",
]
executor.begin_section prod.class, prod.name, "Building"
prod.build(executor, build.crossruby, extra_mkargs)
executor.end_section prod.class, prod.name
end
options = @packager.full_build_options
target_triplet = options[:target]

local_path = File.join("bundle", target_triplet)
env = {
"BUNDLE_APP_CONFIG" => File.join(".bundle", target_triplet),
"BUNDLE_PATH" => local_path,
"BUNDLE_WITHOUT" => "build",
# Do not auto-switch bundler version by Gemfile.lock
"BUNDLE_VERSION" => "system",
# FIXME: BUNDLE_PATH is set as a installation destination here, but
# it is also used as a source of gems to be loaded by RubyGems itself.
# RubyGems loads "psych" gem and if Gemfile includes "psych" gem,
# RubyGems tries to load "psych" gem from BUNDLE_PATH at the second
# time of "bundle install" command. But the extension of "psych" gem
# under BUNDLE_PATH is built for Wasm target, not for host platform,
# so it fails to load the extension.
#
# Thus we preload psych from the default LOAD_PATH here to avoid
# loading Wasm version of psych.so via `Kernel#require` patched by
# RubyGems.
"RUBYOPT" => "-rpsych",
}

args = [
File.join(baseruby.install_dir, "bin", "bundle"),
"install",
"--standalone",
"--target-rbconfig",
rbconfig_rb,
]

executor.system(*args, env: env)
executor.cp_r(local_path, gem_home)
end

def cache_key(digest)
Expand Down Expand Up @@ -337,6 +344,14 @@ def build_gem_exts(executor, gem_home)
# No-op because we already built extensions as part of the Ruby build
end

def wasi_exec_model
# TODO: Detect WASI exec-model from binary exports (_start or _initialize)
use_js_gem = @packager.specs.any? do |spec|
spec.name == "js"
end
use_js_gem ? "reactor" : "command"
end

def link_gem_exts(executor, ruby_root, gem_home, module_bytes)
return module_bytes unless @packager.features.support_component_model?

Expand Down
1 change: 0 additions & 1 deletion lib/ruby_wasm/packager/file_system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ def remove_stdlib_component(executor, component)
when "enc"
# Remove all encodings except for encdb.so and transdb.so
enc_dir = File.join(@ruby_root, "lib", "ruby", ruby_version, "wasm32-wasi", "enc")
puts File.join(enc_dir, "**/*.so")
Dir.glob(File.join(enc_dir, "**/*.so")).each do |entry|
next if entry.end_with?("encdb.so", "transdb.so")
RubyWasm.logger.debug "Removing stdlib encoding: #{entry}"
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_wasm/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module RubyWasm
VERSION = "2.6.2"
VERSION = "2.6.2.dev"
end
2 changes: 1 addition & 1 deletion packages/gems/js/ext/js/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

$objs = %w[js-core.o witapi-core.o]

use_component_model = enable_config("component-model", false)
use_component_model = enable_config("component-model", true)
$stderr.print "Building with component model: "
$stderr.puts use_component_model ? "\e[1;32myes\e[0m" : "\e[1;31mno\e[0m"
if use_component_model
Expand Down
2 changes: 1 addition & 1 deletion packages/gems/js/lib/js/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module JS
VERSION = "2.6.2"
VERSION = "2.6.2.dev"
end
1 change: 1 addition & 0 deletions packages/npm-packages/ruby-3.2-wasm-wasi/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.tgz
/bundle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
PATH
remote: ../../..
specs:
ruby_wasm (2.6.2)
ruby_wasm (2.6.2.dev)

PATH
remote: ../../gems/js
specs:
js (2.6.2)
js (2.6.2.dev)

GEM
remote: https://rubygems.org/
Expand All @@ -26,4 +26,4 @@ DEPENDENCIES
test-unit

BUNDLED WITH
2.5.3
2.5.9
1 change: 1 addition & 0 deletions packages/npm-packages/ruby-3.3-wasm-wasi/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.tgz
/bundle
8 changes: 8 additions & 0 deletions packages/npm-packages/ruby-3.3-wasm-wasi/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "js", path: "../../gems/js"
gem "ruby_wasm", path: "../../../"
gem "power_assert"
gem "test-unit"
29 changes: 29 additions & 0 deletions packages/npm-packages/ruby-3.3-wasm-wasi/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PATH
remote: ../../..
specs:
ruby_wasm (2.6.2.dev)

PATH
remote: ../../gems/js
specs:
js (2.6.2.dev)

GEM
remote: https://rubygems.org/
specs:
power_assert (2.0.3)
test-unit (3.6.2)
power_assert

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
js!
power_assert
ruby_wasm!
test-unit

BUNDLED WITH
2.5.9
2 changes: 2 additions & 0 deletions packages/npm-packages/ruby-head-wasm-wasi/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.tgz
/tmp
/bundle
/vendor
Loading
Loading