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
11 changes: 11 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(bazel build *)",
"Bash(bazel clean *)",
"Bash(bazel query *)",
"Bash(bazel run *)",
"Bash(bazel test *)"
]
}
}
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,17 @@ ruby.toolchain(

**Notes:**

- Portable Ruby is only supported on Linux (arm64, x86_64) and macOS (arm64).
- Portable Ruby is only supported on Linux (arm64, x86_64) and macOS (arm64, x86_64).
- Setting `portable_ruby = True` has no effect on JRuby, TruffleRuby, or Windows.
- On Windows, the toolchain automatically falls back to RubyInstaller.
- Find available portable Ruby releases at https://github.com/bazel-contrib/portable-ruby/releases
- Portable Ruby toolchains are multi-platform and can be used on for [Remote Build Execution][15].

### JRuby

On all operating systems, JRuby is downloaded manually.
It uses Bazel runtime Java toolchain as JDK.
JRuby is currently the only toolchain that supports [Remote Build Execution][15].
JRuby toolchain is cross-platform and fully supports [Remote Build Execution][15].

### TruffleRuby

Expand Down
19 changes: 18 additions & 1 deletion docs/repository_rules.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions ruby/extensions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ load("//ruby/private:download.bzl", "RUBY_BUILD_VERSION")
load("//ruby/private:toolchain.bzl", "DEFAULT_RUBY_REPOSITORY")
load(":deps.bzl", "rb_bundle", "rb_bundle_fetch", "rb_register_toolchains")

def _resolve_version(module_ctx, toolchain):
"""Resolve the Ruby version string from `version` or `version_file`.

`rb_register_toolchains` needs to know whether the requested engine is
JRuby to decide between the multi-platform `portable_ruby` path and the
single-platform path. When the user supplies `version_file` instead of an
explicit `version`, the extension reads the file here so the decision can
be made at extension-evaluation time, before the repo rules fire.
"""
if toolchain.version:
return toolchain.version
if not toolchain.version_file:
return None
content = module_ctx.read(toolchain.version_file).strip("\r\n")
if toolchain.version_file.name == ".tool-versions":
for line in content.splitlines():
if line.startswith("ruby"):
return line.partition(" ")[-1]
return None
return content

ruby_bundle = tag_class(attrs = {
"name": attr.string(doc = "Resulting repository name for the bundle"),
"srcs": attr.label_list(),
Expand Down Expand Up @@ -115,6 +136,7 @@ def _ruby_module_extension(module_ctx):
toolchain.portable_ruby,
toolchain.portable_ruby_release_suffix,
toolchain.portable_ruby_checksums,
_resolve_version(module_ctx, toolchain),
)
if module_ctx.is_dev_dependency(toolchain):
direct_dev_dep_names.append(toolchain.name)
Expand All @@ -132,6 +154,7 @@ def _ruby_module_extension(module_ctx):
portable_ruby,
portable_ruby_release_suffix,
portable_ruby_checksums,
resolved_version,
) = config
rb_register_toolchains(
name = name,
Expand All @@ -142,6 +165,7 @@ def _ruby_module_extension(module_ctx):
portable_ruby = portable_ruby,
portable_ruby_release_suffix = portable_ruby_release_suffix,
portable_ruby_checksums = portable_ruby_checksums,
resolved_version = resolved_version,
register = False,
)

Expand Down
7 changes: 6 additions & 1 deletion ruby/private/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ bzl_library(
visibility = ["//ruby:__subpackages__"],
deps = [
":download",
"//ruby/private/toolchain:hub",
"//ruby/private/toolchain:platforms",
"//ruby/private/toolchain:repository_proxy",
],
)
Expand Down Expand Up @@ -132,7 +134,10 @@ bzl_library(
name = "download",
srcs = ["download.bzl"],
visibility = ["//ruby:__subpackages__"],
deps = [":portable_ruby_checksums"],
deps = [
":portable_ruby_checksums",
"//ruby/private/toolchain:platforms",
],
)

bzl_library(
Expand Down
94 changes: 65 additions & 29 deletions ruby/private/download.bzl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"Repository rule for fetching Ruby interpreters"

load("//ruby/private:portable_ruby_checksums.bzl", "PORTABLE_RUBY_CHECKSUMS", "PORTABLE_RUBY_DEFAULT_SUFFIXES")
load(
"//ruby/private/toolchain:platforms.bzl",
"MULTI_PLATFORM_RUBY_PLATFORMS",
"PORTABLE_RUBY_PLATFORMS",
)

RUBY_BUILD_VERSION = "20260512"

Expand Down Expand Up @@ -89,6 +94,34 @@ load Gem.bin_path("bundler", "bundle", version)
end
"""

def _resolve_platform(repository_ctx):
"""Return the canonical platform key (e.g. 'x86_64_linux').

Honors the explicit `platform` attr when set; otherwise infers from host.
"""
if repository_ctx.attr.platform:
return repository_ctx.attr.platform

os_name = repository_ctx.os.name
if os_name.startswith("mac"):
os_key = "darwin"
elif os_name.startswith("linux"):
os_key = "linux"
elif os_name.startswith("windows"):
os_key = "windows"
else:
os_key = os_name

arch = repository_ctx.os.arch
if arch in ["amd64", "x86_64"]:
arch_key = "x86_64"
elif arch in ["arm64", "aarch64"]:
arch_key = "arm64"
else:
arch_key = arch

return "{}_{}".format(arch_key, os_key)

def _rb_download_impl(repository_ctx):
if repository_ctx.attr.version and not repository_ctx.attr.version_file:
version = repository_ctx.attr.version
Expand All @@ -104,6 +137,9 @@ def _rb_download_impl(repository_ctx):
ruby_binary_name = "ruby"
gem_binary_name = "gem"

platform = _resolve_platform(repository_ctx)
is_windows_target = platform.endswith("_windows")

if version.startswith("jruby"):
_install_jruby(repository_ctx, version)

Expand Down Expand Up @@ -134,12 +170,17 @@ def _rb_download_impl(repository_ctx):
env.update({"OPENSSL_PREFIX": openssl_prefix})
elif version == "system":
engine = _symlink_system_ruby(repository_ctx)
elif repository_ctx.os.name.startswith("windows"):
elif is_windows_target:
# Windows MRI uses RubyInstaller — portable-ruby does not publish
# Windows tarballs. Applies whether the platform was inferred from the
# host (single-platform mode) or set explicitly via the `platform` attr
# (multi-platform mode picking the Windows per-platform repo).
_install_via_rubyinstaller(repository_ctx, version)
elif repository_ctx.attr.portable_ruby:
_install_portable_ruby(
repository_ctx,
version,
platform,
repository_ctx.attr.portable_ruby_checksums,
)

Expand Down Expand Up @@ -219,9 +260,11 @@ def _install_jruby(repository_ctx, version):
if sha256 != download.sha256:
print(_JRUBY_INTEGRITY_MISSING.format(sha256 = download.sha256, version = version)) # buildifier: disable=print

if repository_ctx.os.name.startswith("windows"):
repository_ctx.symlink("dist/bin/bundle.bat", "dist/bin/bundle.cmd")
repository_ctx.symlink("dist/bin/jgem.bat", "dist/bin/jgem.cmd")
# Always create the Windows `.cmd` wrappers — JRuby's archive is the same
# across platforms, and the BUILD.tpl select picks `bundle.cmd` / `jgem.cmd`
# when consumed at a Windows target config. Symlinks are harmless on Unix.
repository_ctx.symlink("dist/bin/bundle.bat", "dist/bin/bundle.cmd")
repository_ctx.symlink("dist/bin/jgem.bat", "dist/bin/jgem.cmd")

# https://github.com/oneclick/rubyinstaller2/wiki/FAQ#q-how-do-i-perform-a-silentunattended-install-with-the-rubyinstaller
def _install_via_rubyinstaller(repository_ctx, version):
Expand Down Expand Up @@ -283,35 +326,20 @@ def _install_via_ruby_build(repository_ctx, version):

repository_ctx.delete("ruby-build")

def _install_portable_ruby(repository_ctx, ruby_version, checksums):
def _install_portable_ruby(repository_ctx, ruby_version, platform, checksums):
"""Install portable Ruby from bazel-contrib/portable-ruby project.

Args:
repository_ctx: Repository context
ruby_version: Ruby version (e.g., "3.4.8")
checksums: Dict mapping platform keys to SHA256 checksums
platform: Canonical platform key (e.g., "x86_64_linux")
checksums: Dict mapping artifact names to SHA256 checksums
"""

# Detect platform
os_name = repository_ctx.os.name
if os_name.startswith("mac"):
os_key = "darwin"
elif os_name.startswith("linux"):
os_key = "linux"
else:
os_key = os_name

# Detect architecture
arch = repository_ctx.os.arch
if arch == "amd64":
arch_key = "x86_64"
elif arch in ["arm64", "aarch64"]:
arch_key = "arm64"
else:
arch_key = arch

# Combine to form platform key
platform_key = arch_key + "_" + os_key
if platform not in PORTABLE_RUBY_PLATFORMS:
fail("portable Ruby is not available for platform {}; supported: {}".format(
platform,
sorted(PORTABLE_RUBY_PLATFORMS),
))

# Determine release suffix: explicit attr overrides built-in default
suffix = repository_ctx.attr.portable_ruby_release_suffix
Expand All @@ -320,7 +348,7 @@ def _install_portable_ruby(repository_ctx, ruby_version, checksums):

artifact_name = _PORTABLE_RUBY_NAME.format(
version = ruby_version,
platform = platform_key,
platform = platform,
)

# Get checksum if provided (Bazel will warn if not provided)
Expand All @@ -331,7 +359,7 @@ def _install_portable_ruby(repository_ctx, ruby_version, checksums):
kwargs["sha256"] = PORTABLE_RUBY_CHECKSUMS[suffix][artifact_name]

repository_ctx.report_progress(
"Downloading portable Ruby %s-%s for %s" % (ruby_version, suffix, platform_key),
"Downloading portable Ruby %s-%s for %s" % (ruby_version, suffix, platform),
)
repository_ctx.download_and_extract(
url = _PORTABLE_RUBY_URL.format(
Expand Down Expand Up @@ -422,6 +450,14 @@ Keys: artifact names (e.g., ruby-3.4.8.x86_64_linux.tar.gz, ruby-3.4.8.arm64_dar
Values: SHA256 checksums for the corresponding platform.
""",
),
"platform": attr.string(
doc = """
Explicit canonical platform key (e.g. `x86_64_linux`). When set, overrides host
auto-detection and is used to select the right portable Ruby download. Used by
`rb_register_toolchains` when registering per-platform toolchains.
""",
values = [""] + MULTI_PLATFORM_RUBY_PLATFORMS,
),
"_build_tpl": attr.label(
allow_single_file = True,
default = "@rules_ruby//:ruby/private/download/BUILD.tpl",
Expand Down
Loading
Loading