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
24 changes: 17 additions & 7 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,16 @@ ruby_bundle(
# You can specify more than one bundle in the WORKSPACE file
ruby_bundle(
name = "bundle_app_shopping",
gemfile = "//apps/shopping:Gemfile",
gemfile_lock = "//apps/shopping:Gemfile.lock",
gemfile = "//:apps/shopping/Gemfile",
gemfile_lock = "//:apps/shopping/Gemfile.lock",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why the : is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original documentation is not working because the colon should preceed directory name. At least that's what I had to do in Selenium where all Ruby code lives in rb directory (https://github.com/SeleniumHQ/selenium/blob/trunk/WORKSPACE#L286):

ruby_bundle(
    name = "bundle",
    srcs = [
        "//:rb/lib/selenium/devtools/version.rb",
        "//:rb/lib/selenium/webdriver/version.rb",
        "//:rb/selenium-devtools.gemspec",
        "//:rb/selenium-webdriver.gemspec",
    ],
    gemfile = "//:rb/Gemfile",
)

)

# You can also install from Gemfile using `gemspec`.
ruby_bundle(
name = "bundle_gemspec",
srcs = ["//:lib/my_gem/my_gem.gemspec"],
gemfile = "//:lib/my_gem/Gemfile",
gemfile_lock = "//:lib/my_gem/Gemfile.lock",
)
----

Expand Down Expand Up @@ -477,6 +485,7 @@ ruby_bundle(
bundler_version = "2.1.4",
includes = {},
excludes = {},
srcs = [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we not have src here before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this attribute.

vendor_cache = False,
ruby_sdk = "@org_ruby_lang_ruby_toolchain",
ruby_interpreter = "@org_ruby_lang_ruby_toolchain//:ruby",
Expand All @@ -497,12 +506,17 @@ A unique name for this rule.
The `Gemfile` which Bundler runs with.

|`gemfile_lock` a|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be now called gemfile? Since we compute the lock file automatically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I'll fix it so that it still respects gemfile_lock when it's passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed fixup commit 7f192df which addresses this and actually makes less changes to existing. If it's good, I'll rebase and squash the commits together.

`Label, required`
`Label, optional`

The `Gemfile.lock` which Bundler runs with.

NOTE: This rule never updates the `Gemfile.lock`. It is your responsibility to generate/update `Gemfile.lock`

|`srcs` a|
`List of Labels, optional`

List of additional files required for Bundler to install gems. This could usually include `*.gemspec` files.

|`vendor_cache` a|
`Bool, optional`

Expand All @@ -529,10 +543,6 @@ List of glob patterns per gem to be excluded from the library. Keys are the name
|===


==== Limitations

Installing using a `Gemfile` that uses the `gemspec` keyword is not currently supported.

==== Conventions

`ruby_bundle` creates several targets that can be used downstream. In the examples below we assume that your `ruby_bundle` has a name `app_bundle`:
Expand Down
19 changes: 13 additions & 6 deletions ruby/private/bundle/create_bundle_build_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,28 @@

# For ordinary gems, this path is like 'lib/ruby/3.0.0/gems/rspec-3.10.0'.
# For gems with native extension installed via prebuilt packages, the last part of this path can
# contain an OS-specific suffix like 'grpc-1.38.0-universal-darwin' or 'grpc-1.38.0-x86_64-linux'
# contain an OS-specific suffix like 'grpc-1.38.0-universal-darwin' or 'grpc-1.38.0-x86_64-linux'
# instead of 'grpc-1.38.0'.
#
# Since OS platform is unlikely to change between Bazel builds on the same machine,
#
# Since OS platform is unlikely to change between Bazel builds on the same machine,
# `#{gem_name}-#{gem_version}*` would be sufficient to narrow down matches to at most one.
#
# Library path differs across implementations as `lib/ruby` on MRI and `lib/jruby` on JRuby.
GEM_PATH = ->(ruby_version, gem_name, gem_version) do
Dir.glob("lib/ruby/#{ruby_version}/gems/#{gem_name}-#{gem_version}*").first
Dir.glob("lib/#{RbConfig::CONFIG['RUBY_INSTALL_NAME']}/#{ruby_version}/gems/#{gem_name}-#{gem_version}*").first
end

# For ordinary gems, this path is like 'lib/ruby/3.0.0/specifications/rspec-3.10.0.gemspec'.
# For gems with native extension installed via prebuilt packages, the last part of this path can
# contain an OS-specific suffix like 'grpc-1.38.0-universal-darwin.gemspec' or
# contain an OS-specific suffix like 'grpc-1.38.0-universal-darwin.gemspec' or
# 'grpc-1.38.0-x86_64-linux.gemspec' instead of 'grpc-1.38.0.gemspec'.
#
# Since OS platform is unlikely to change between Bazel builds on the same machine,
# `#{gem_name}-#{gem_version}*.gemspec` would be sufficient to narrow down matches to at most one.
#
# Library path differs across implementations as `lib/ruby` on MRI and `lib/jruby` on JRuby.
SPEC_PATH = ->(ruby_version, gem_name, gem_version) do
Dir.glob("lib/ruby/#{ruby_version}/specifications/#{gem_name}-#{gem_version}*.gemspec").first
Dir.glob("lib/#{RbConfig::CONFIG['RUBY_INSTALL_NAME']}/#{ruby_version}/specifications/#{gem_name}-#{gem_version}*.gemspec").first
end

require 'bundler'
Expand Down Expand Up @@ -247,6 +251,9 @@ def remove_bundler_version!
end

def register_gem(spec, template_out, bundle_lib_paths, bundle_binaries)
# Do not register local gems
return if spec.source.path?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain that line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible to register local gems as they are not linking to external/bundle. This guard just prevents the code from even trying to do so.


gem_path = GEM_PATH[ruby_version, spec.name, spec.version]
spec_path = SPEC_PATH[ruby_version, spec.name, spec.version]
base_dir = "lib/ruby/#{ruby_version}"
Expand Down
34 changes: 23 additions & 11 deletions ruby/private/bundle/def.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,20 @@ def install_bundler(runtime_ctx, bundler_version):
)

def bundle_install(runtime_ctx, previous_result):
cwd = runtime_ctx.ctx.path(".")
bundler_args = [
"install",
"--binstubs={}".format(cwd.get_child(BUNDLE_BIN_PATH)),
"--path={}".format(cwd.get_child(BUNDLE_PATH)),
"--standalone",
"--gemfile={}".format(runtime_ctx.ctx.attr.gemfile.name),
]
if runtime_ctx.ctx.attr.gemfile_lock:
bundler_args += ["--deployment", "--frozen"]

result = run_bundler(
runtime_ctx,
[
"install",
"--binstubs={}".format(BUNDLE_BIN_PATH),
"--path={}".format(BUNDLE_PATH),
"--deployment",
"--standalone",
"--frozen",
],
bundler_args,
previous_result,
)

Expand All @@ -133,6 +137,11 @@ def bundle_install(runtime_ctx, previous_result):
return result

def generate_bundle_build_file(runtime_ctx, previous_result):
if runtime_ctx.ctx.attr.gemfile_lock:
gemfile_lock = runtime_ctx.ctx.attr.gemfile_lock.name
else:
gemfile_lock = "{}.lock".format(runtime_ctx.ctx.attr.gemfile.name)

# Create the BUILD file to expose the gems to the WORKSPACE
# USAGE: ./create_bundle_build_file.rb BUILD.bazel Gemfile.lock repo-name [excludes-json] workspace-name
args = [
Expand All @@ -142,7 +151,7 @@ def generate_bundle_build_file(runtime_ctx, previous_result):
"bundler/lib",
SCRIPT_BUILD_FILE_GENERATOR, # The template used to created bundle file
"BUILD.bazel", # Bazel build file (can be empty)
"Gemfile.lock", # Gemfile.lock where we list all direct and transitive dependencies
gemfile_lock, # Gemfile.lock where we list all direct and transitive dependencies
runtime_ctx.ctx.name, # Name of the target
repr(runtime_ctx.ctx.attr.includes),
repr(runtime_ctx.ctx.attr.excludes),
Expand All @@ -154,15 +163,18 @@ def generate_bundle_build_file(runtime_ctx, previous_result):
fail("build file generation failed: %s%s" % (result.stdout, result.stderr))

def _ruby_bundle_impl(ctx):
ctx.symlink(ctx.attr.gemfile, "Gemfile")
ctx.symlink(ctx.attr.gemfile_lock, "Gemfile.lock")
ctx.symlink(ctx.attr.gemfile, ctx.attr.gemfile.name)
if ctx.attr.gemfile_lock:
ctx.symlink(ctx.attr.gemfile_lock, ctx.attr.gemfile_lock.name)
if ctx.attr.vendor_cache:
ctx.symlink(
ctx.path(str(ctx.path(ctx.attr.gemfile).dirname) + "/vendor"),
ctx.path("vendor"),
)
ctx.symlink(ctx.attr._create_bundle_build_file, SCRIPT_BUILD_FILE_GENERATOR)
ctx.symlink(ctx.attr._install_bundler, SCRIPT_INSTALL_GEM)
for src in ctx.attr.srcs:
ctx.symlink(src, src.name)

bundler_version = ctx.attr.bundler_version

Expand Down
3 changes: 3 additions & 0 deletions ruby/private/constants.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ BUNDLE_ATTRS = {
"gemfile_lock": attr.label(
allow_single_file = True,
),
"srcs": attr.label_list(
allow_files = True,
),
"vendor_cache": attr.bool(
doc = "Symlink the vendor directory into the Bazel build space, this allows Bundler to access vendored Gems",
),
Expand Down
2 changes: 2 additions & 0 deletions ruby/private/toolchains/repository_context.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def ruby_repository_context(repository_ctx, interpreter_path):
rel_interpreter_path = str(interpreter_path)
if rel_interpreter_path.startswith("/"):
rel_interpreter_path = rel_interpreter_path[1:]
elif rel_interpreter_path.startswith("C:/"):
rel_interpreter_path = rel_interpreter_path[3:]

return struct(
# Location of the interpreter
Expand Down
15 changes: 15 additions & 0 deletions ruby/private/toolchains/ruby_runtime.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def _relativate(path):
# TODO(yugui) support windows
if path.startswith("/"):
return path[1:]
elif path.startswith("C:/"):
return path[3:]
else:
return path

Expand All @@ -44,6 +46,19 @@ def _list_libdirs(ruby):

def _install_dirs(ctx, ruby, *names):
paths = sorted([ruby.rbconfig(ruby, name) for name in names])

# JRuby reports some of the directories as nulls.
paths = [path for path in paths if path]

# Sometimes we end up with the same directory multiple times
# so make sure paths are unique by converting it to set.
# For example, this is what we have on Fedora 34:
# $ ruby -rrbconfig -e "p RbConfig::CONFIG['rubyhdrdir']"
# "/usr/include"
# $ ruby -rrbconfig -e "p RbConfig::CONFIG['rubyarchhdrdir']"
# "/usr/include"
paths = depset(paths).to_list()

rel_paths = [_relativate(path) for path in paths]
for i, (path, rel_path) in enumerate(zip(paths, rel_paths)):
if not _is_subpath(path, paths[:i]):
Expand Down