diff --git a/README.adoc b/README.adoc index 7fd3c7e..1fe1ba6 100644 --- a/README.adoc +++ b/README.adoc @@ -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", +) + +# 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", ) ---- @@ -477,6 +485,7 @@ ruby_bundle( bundler_version = "2.1.4", includes = {}, excludes = {}, + srcs = [], vendor_cache = False, ruby_sdk = "@org_ruby_lang_ruby_toolchain", ruby_interpreter = "@org_ruby_lang_ruby_toolchain//:ruby", @@ -497,12 +506,17 @@ A unique name for this rule. The `Gemfile` which Bundler runs with. |`gemfile_lock` a| -`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` @@ -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`: diff --git a/ruby/private/bundle/create_bundle_build_file.rb b/ruby/private/bundle/create_bundle_build_file.rb index bc0b784..e832310 100755 --- a/ruby/private/bundle/create_bundle_build_file.rb +++ b/ruby/private/bundle/create_bundle_build_file.rb @@ -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' @@ -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? + 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}" diff --git a/ruby/private/bundle/def.bzl b/ruby/private/bundle/def.bzl index 1e1243b..e368bd4 100644 --- a/ruby/private/bundle/def.bzl +++ b/ruby/private/bundle/def.bzl @@ -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, ) @@ -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 = [ @@ -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), @@ -154,8 +163,9 @@ 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"), @@ -163,6 +173,8 @@ def _ruby_bundle_impl(ctx): ) 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 diff --git a/ruby/private/constants.bzl b/ruby/private/constants.bzl index d615ea6..a76f8f3 100644 --- a/ruby/private/constants.bzl +++ b/ruby/private/constants.bzl @@ -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", ), diff --git a/ruby/private/toolchains/repository_context.bzl b/ruby/private/toolchains/repository_context.bzl index b151531..221a4ac 100644 --- a/ruby/private/toolchains/repository_context.bzl +++ b/ruby/private/toolchains/repository_context.bzl @@ -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 diff --git a/ruby/private/toolchains/ruby_runtime.bzl b/ruby/private/toolchains/ruby_runtime.bzl index 048084f..d3ec3ed 100644 --- a/ruby/private/toolchains/ruby_runtime.bzl +++ b/ruby/private/toolchains/ruby_runtime.bzl @@ -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 @@ -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]):