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
6 changes: 6 additions & 0 deletions ruby/extensions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ ruby_bundle_fetch = tag_class(attrs = {
"name": attr.string(doc = "Resulting repository name for the bundle"),
"srcs": attr.label_list(),
"env": attr.string_dict(),
"extra_args": attr.string_list(doc = "Extra arguments appended to `bundle install`. Supports `$(location ...)` against `data`."),
"data": attr.label_list(doc = "Files referenced from `extra_args` via `$(location ...)`."),
"binstubs": attr.bool(default = True, doc = "Run `bundle binstubs --all` after install. Set False for cross-platform bundles."),
"gemfile": attr.label(),
"gemfile_lock": attr.label(),
"gem_checksums": attr.string_dict(),
Expand Down Expand Up @@ -99,6 +102,9 @@ def _ruby_module_extension(module_ctx):
name = bundle_fetch.name,
srcs = bundle_fetch.srcs,
env = bundle_fetch.env,
extra_args = bundle_fetch.extra_args,
data = [str(label) for label in bundle_fetch.data],
binstubs = bundle_fetch.binstubs,
gemfile = bundle_fetch.gemfile,
gemfile_lock = bundle_fetch.gemfile_lock,
gem_checksums = bundle_fetch.gem_checksums,
Expand Down
24 changes: 23 additions & 1 deletion ruby/private/bundle_fetch.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ def _rb_bundle_fetch_impl(repository_ctx):
"{gem_fragments}": "".join(gem_fragments),
"{gem_install_fragments}": "".join(gem_install_fragments),
"{env}": repr(repository_ctx.attr.env),
"{extra_args}": repr(repository_ctx.attr.extra_args),
"{data}": _join_and_indent(repository_ctx.attr.data),
"{binstubs}": repr(repository_ctx.attr.binstubs),
"{ruby}": ruby_toolchain_attr,
},
)
Expand All @@ -258,6 +261,9 @@ def _rb_bundle_fetch_impl(repository_ctx):
"gemfile_lock": repository_ctx.attr.gemfile_lock,
"srcs": repository_ctx.attr.srcs,
"env": repository_ctx.attr.env,
"extra_args": repository_ctx.attr.extra_args,
"data": repository_ctx.attr.data,
"binstubs": repository_ctx.attr.binstubs,
"bundler_remote": repository_ctx.attr.bundler_remote,
"bundler_checksums": repository_ctx.attr.bundler_checksums,
"gem_checksums": gem_checksums,
Expand Down Expand Up @@ -299,7 +305,23 @@ rb_bundle_fetch = repository_rule(
doc = "List of Ruby source files necessary during installation.",
),
"env": attr.string_dict(
doc = "Environment variables to use during installation.",
doc = "Environment variables to use during installation. Values support " +
"`$(location ...)` expansion against `data` (forwarded to the " +
"generated rb_bundle_install `env`).",
),
"extra_args": attr.string_list(
doc = "Extra arguments appended to the `bundle install` command line " +
"run by the generated rb_bundle_install target. Supports " +
"`$(location ...)` expansion against `data`.",
),
"binstubs": attr.bool(
default = True,
doc = "Forwarded to the generated rb_bundle_install `binstubs` (set False " +
"to skip `bundle binstubs --all` for cross-platform bundles).",
),
"data": attr.string_list(
doc = "Labels (as canonical strings) referenced from `extra_args` via " +
"`$(location ...)`; forwarded to the generated rb_bundle_install `data`.",
),
"bundler_remote": attr.string(
default = "https://rubygems.org/",
Expand Down
3 changes: 3 additions & 0 deletions ruby/private/bundle_fetch/BUILD.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ package(default_visibility = ["//visibility:public"])
rb_bundle_install(
name = "{name}",
srcs = {srcs},
data = {data},
env = {env},
extra_args = {extra_args},
binstubs = {binstubs},
gemfile = "{gemfile_path}",
gemfile_lock = "{gemfile_lock_path}",
jars = glob(["{jars_path}/**/*.jar"], allow_empty = True),
Expand Down
66 changes: 60 additions & 6 deletions ruby/private/bundle_install.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,20 @@ def _rb_bundle_install_impl(ctx):

jar_files = ctx.files.jars if ctx.attr.jars else []

# Expand `$(location ...)`/`$(execpath ...)` in env values (against `data`),
# mirroring `extra_args`. Lets an env var reference a build artifact by label
# — e.g. prepending a generated cross-compiler wrapper dir onto PATH.
attr_env = {
key: ctx.expand_location(value, ctx.attr.data)
for key, value in ctx.attr.env.items()
}

env = {}
env.update(toolchain.env)
env.update(ctx.attr.env)
env.update(attr_env)

bundler_env = {}
bundler_env.update(ctx.attr.env)
bundler_env.update(attr_env)
jars_home_strip_suffix = ""

if toolchain.version.startswith("jruby"):
Expand All @@ -80,12 +88,12 @@ def _rb_bundle_install_impl(ctx):
if _is_windows(ctx):
script = ctx.actions.declare_file("bundle_install_{}.cmd".format(ctx.label.name))
template = ctx.file._bundle_install_cmd_tpl
path = ctx.attr.env.get("PATH", "%PATH%")
path = attr_env.get("PATH", "%PATH%")
env.update({"PATH": _normalize_path(ctx, toolchain.ruby.dirname) + ";" + path})
else:
script = ctx.actions.declare_file("bundle_install_{}.sh".format(ctx.label.name))
template = ctx.file._bundle_install_sh_tpl
path = ctx.attr.env.get("PATH", "$PATH")
path = attr_env.get("PATH", "$PATH")
env.update({"PATH": toolchain.ruby.dirname + ":" + path})

# Calculate relative location between BUNDLE_GEMFILE and BUNDLE_PATH.
Expand All @@ -105,19 +113,39 @@ def _rb_bundle_install_impl(ctx):
"BUNDLE_SHEBANG": _normalize_path(ctx, toolchain.ruby.short_path),
})

# Binstubs generation runs with the HOST ruby, which validates gems against
# the running platform. For a cross-platform bundle (e.g. installed with
# --target-rbconfig for another OS/arch) the host can't see those gems'
# native extensions and `binstubs --all` fails. `binstubs = False` skips it,
# just materializing the (empty) declared binstubs dir instead.
if ctx.attr.binstubs:
binstubs_cmd = "{} {} binstubs --all".format(
_normalize_path(ctx, toolchain.ruby.path),
_normalize_path(ctx, bundler_exe),
)
elif _is_windows(ctx):
binstubs_cmd = 'if not exist "{p}" mkdir "{p}"'.format(p = _normalize_path(ctx, binstubs.path))
else:
binstubs_cmd = 'mkdir -p "{}"'.format(binstubs.path)

ctx.actions.expand_template(
template = template,
output = script,
substitutions = {
"{env}": _convert_env_to_script(ctx, env),
"{bundler_exe}": _normalize_path(ctx, bundler_exe),
"{ruby_path}": _normalize_path(ctx, toolchain.ruby.path),
"{binstubs_cmd}": binstubs_cmd,
"{extra_args}": " ".join([
ctx.expand_location(arg, ctx.attr.data)
for arg in ctx.attr.extra_args
]),
},
)

ctx.actions.run(
executable = script,
inputs = depset([ctx.file.gemfile, ctx.file.gemfile_lock] + ctx.files.srcs + ctx.files.gems + jar_files),
inputs = depset([ctx.file.gemfile, ctx.file.gemfile_lock] + ctx.files.srcs + ctx.files.data + ctx.files.gems + jar_files),
outputs = [binstubs, bundle_path],
mnemonic = "BundleInstall",
progress_message = "Running bundle install (%{label})",
Expand All @@ -137,6 +165,11 @@ def _rb_bundle_install_impl(ctx):
files = depset(files),
runfiles = ctx.runfiles(files),
),
# `gems` exposes JUST the installed vendor/bundle tree (no Gemfile/
# binstubs), so consumers can package it cleanly — e.g.
# `filegroup(output_group = "gems")` + pkg_files strip_prefix to lay the
# gems into a container's BUNDLE_PATH without the surrounding files.
OutputGroupInfo(gems = depset([bundle_path])),
RubyFilesInfo(
binary = None,
transitive_srcs = depset([ctx.file.gemfile, ctx.file.gemfile_lock] + ctx.files.srcs),
Expand Down Expand Up @@ -183,7 +216,28 @@ rb_bundle_install = rule(
doc = "List of Ruby source files used to build the library.",
),
"env": attr.string_dict(
doc = "Environment variables to use during installation.",
doc = "Environment variables to use during installation. Values support " +
"`$(location ...)`/`$(execpath ...)` make-variable expansion against " +
"`data` (e.g. prepend a generated cross-compiler wrapper dir onto PATH).",
),
"binstubs": attr.bool(
default = True,
doc = "Whether to run `bundle binstubs --all` after install. Set False for " +
"cross-platform bundles (installed with a foreign --target-rbconfig): the " +
"host ruby can't validate the target's native extensions, so binstubs " +
"generation fails. When False the (empty) binstubs dir is still created.",
),
"extra_args": attr.string_list(
doc = "Extra arguments appended to the `bundle install` command line. " +
"Supports `$(location ...)`/`$(rootpath ...)`/`$(execpath ...)` make-variable " +
"expansion against `data`. For example " +
"`[\"--target-rbconfig\", \"$(location //path:rbconfig.rb)\"]` " +
"to install a different platform's precompiled gems (cross-platform bundle).",
),
"data": attr.label_list(
allow_files = True,
doc = "Files referenced from `extra_args` via `$(location ...)` expansion. " +
"They are also added as inputs to the `bundle install` action.",
),
"ruby": attr.label(
doc = "Override Ruby toolchain to use when installing the gem.",
Expand Down
4 changes: 2 additions & 2 deletions ruby/private/bundle_install/bundle_install.cmd.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{env}

{ruby_path} {bundler_exe} install --standalone --local
{ruby_path} {bundler_exe} binstubs --all
{ruby_path} {bundler_exe} install --standalone --local {extra_args}
{binstubs_cmd}

:: vim: ft=dosbatch
4 changes: 2 additions & 2 deletions ruby/private/bundle_install/bundle_install.sh.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{env}

{ruby_path} {bundler_exe} install --standalone --local
{ruby_path} {bundler_exe} binstubs --all
{ruby_path} {bundler_exe} install --standalone --local {extra_args}
{binstubs_cmd}

# vim: ft=bash
24 changes: 24 additions & 0 deletions ruby/private/download/BUILD.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ filegroup(
}),
)

# The complete Ruby install tree (bin/, lib/, include/, ...). Useful for
# packaging the interpreter into a container image (portable-ruby is relocatable
# via relative rpaths, so this tars cleanly to e.g. /usr/local).
filegroup(
name = "dist_files",
srcs = glob(
["dist/**/*"],
allow_empty = True,
),
)

# The interpreter's own rbconfig.rb (a single file). Because portable-ruby is
# relocatable (rbconfig computes TOPDIR from __FILE__), passing this unmodified
# to `gem install --target-rbconfig` cross-compiles source gems for THIS Ruby's
# platform — its rubyhdrdir/libdir auto-resolve to the staged `dist` tree. Under
# a platform transition it resolves to the target arch's Ruby.
filegroup(
name = "rbconfig",
srcs = glob(
["dist/lib/ruby/*/*/rbconfig.rb"],
allow_empty = True,
),
)

rb_binary(
name = "ruby",
main = ":ruby_file",
Expand Down
2 changes: 2 additions & 0 deletions ruby/private/toolchain/hub.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ _STATIC_ALIASES = [
"toolchain",
"headers",
"jars",
"dist_files",
"rbconfig",
]

_CONFIG_SETTING_TPL = """
Expand Down