From 8606809ce8afe553ea4ab6c16b398761eb532101 Mon Sep 17 00:00:00 2001 From: Alex Rodionov Date: Tue, 9 Jan 2024 13:22:38 -0800 Subject: [PATCH 1/2] feat: introduce `rb_bundle_fetch()` repository rule This repository rule deprecates existing `rb_bundle()` and provides a number of benefits over existing implementation: 1. Hermeticity of `bundle install` - it's run as a regular rule. 2. Ruby toolchain no longer needs to be installed during WORKSPACE loading. 2. Downloading of gems is taken care of by Bazel. This also means that gems are stored in a repository cache. 3. RBE is potentially supported (at least with JRuby). The new rule however is not complete yet and lacks few important features that will be added later. 1. Support for gems installed from Git repositories. 2. Support for checksums introduced in Bundler 2.50. For most cases, the migration from `rb_bundle()` to `rb_bundle_fetch()` is straightforward - use the same parameters with an extra `gemfile_lock`. The main breaking change is that your targets that used to depend on Bundler binstubs a new `bin` package. For example: | rb_bundle() | rb_bundle_fetch | |---------------------|--------------------| | @bundle//:bin/rspec | @bundle//bin:rspec | --- .github/workflows/ci.yml | 7 +- README.md | 14 +- docs/repository_rules.md | 66 ++++- docs/rules.md | 128 +++++++++- examples/gem/.bazelrc | 5 - examples/gem/BUILD | 15 +- examples/gem/Gemfile.lock | 2 +- examples/gem/MODULE.bazel | 6 +- examples/gem/WORKSPACE | 6 +- examples/gem/spec/BUILD | 8 +- examples/gem/spec/env_spec.rb | 4 + ruby/BUILD | 4 + ruby/defs.bzl | 6 + ruby/deps.bzl | 2 + ruby/extensions.bzl | 28 ++- ruby/private/BUILD | 49 +++- ruby/private/binary.bzl | 62 ++--- ruby/private/binary/BUILD | 13 +- ruby/private/binary/binary.cmd.tpl | 15 +- ruby/private/binary/binary.sh.tpl | 29 ++- ruby/private/bundle.bzl | 11 + ruby/private/bundle/BUILD.tpl | 5 +- ruby/private/bundle_fetch.bzl | 228 ++++++++++++++++++ ruby/private/bundle_fetch/BUILD | 7 + ruby/private/bundle_fetch/BUILD.tpl | 20 ++ ruby/private/bundle_fetch/bin/BUILD.tpl | 13 + .../bundle_fetch/gemfile_lock_parser.bzl | 191 +++++++++++++++ ruby/private/bundle_install.bzl | 158 ++++++++++++ ruby/private/bundle_install/BUILD | 4 + .../bundle_install/bundle_install.cmd.tpl | 7 + .../bundle_install/bundle_install.sh.tpl | 7 + ruby/private/download.bzl | 40 ++- ruby/private/download/BUILD.tpl | 12 +- ruby/private/gem.bzl | 32 +++ ruby/private/gem_build.bzl | 41 +++- ruby/private/gem_build/BUILD | 1 + ruby/private/gem_build/gem_builder.rb.tpl | 4 +- ruby/private/gem_install.bzl | 137 +++++++++++ ruby/private/gem_install/BUILD | 4 + ruby/private/gem_install/gem_install.cmd.tpl | 16 ++ ruby/private/gem_install/gem_install.sh.tpl | 16 ++ ruby/private/gem_push.bzl | 5 +- ruby/private/library.bzl | 10 +- ruby/private/providers.bzl | 16 +- ruby/private/test.bzl | 6 +- ruby/private/toolchain.bzl | 12 +- .../{binary/rlocation.bzl => utils.bzl} | 80 ++++++ ruby/toolchain.bzl | 4 + 48 files changed, 1437 insertions(+), 119 deletions(-) create mode 100644 ruby/private/bundle_fetch.bzl create mode 100644 ruby/private/bundle_fetch/BUILD create mode 100644 ruby/private/bundle_fetch/BUILD.tpl create mode 100644 ruby/private/bundle_fetch/bin/BUILD.tpl create mode 100644 ruby/private/bundle_fetch/gemfile_lock_parser.bzl create mode 100644 ruby/private/bundle_install.bzl create mode 100644 ruby/private/bundle_install/BUILD create mode 100644 ruby/private/bundle_install/bundle_install.cmd.tpl create mode 100644 ruby/private/bundle_install/bundle_install.sh.tpl create mode 100644 ruby/private/gem.bzl create mode 100644 ruby/private/gem_build/BUILD create mode 100644 ruby/private/gem_install.bzl create mode 100644 ruby/private/gem_install/BUILD create mode 100644 ruby/private/gem_install/gem_install.cmd.tpl create mode 100644 ruby/private/gem_install/gem_install.sh.tpl rename ruby/private/{binary/rlocation.bzl => utils.bzl} (50%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cd6ad49..46c66757 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,6 @@ concurrency: group: ${{ github.ref }} cancel-in-progress: true -env: - JAVA_OPTS: -Djdk.io.File.enableADS=true - jobs: ruleset: name: Ruleset @@ -48,6 +45,10 @@ jobs: - bzlmod - WORKSPACE exclude: + # JRuby with bzlmod fails with long path issues on Windows. + - os: windows + ruby: jruby-9.4.5.0 + mode: bzlmod # TruffleRuby doesn't work on Windows. - os: windows ruby: truffleruby-23.1.1 diff --git a/README.md b/README.md index fa63a0e8..a46161f3 100755 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ rb_register_toolchains( ```bazel # WORKSPACE -load("@rules_ruby//ruby:deps.bzl", "rb_bundle") +load("@rules_ruby//ruby:deps.bzl", "rb_bundle_fetch") -rb_bundle( +rb_bundle_fetch( name = "bundle", - srcs = ["//:Gemfile.lock"], gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", ) ``` @@ -64,11 +64,10 @@ use_repo(ruby, "ruby") ```bazel # MODULE.bazel -ruby.bundle( +ruby.bundle_fetch( name = "bundle", - srcs = ["//:Gemfile.lock"], gemfile = "//:Gemfile", - toolchain = "@ruby//:BUILD", + gemfile_lock = "//:Gemfile.lock", ) use_repo(ruby, "bundle", "ruby_toolchains") ``` @@ -137,11 +136,10 @@ However, some are known not to work or work only partially (e.g. mRuby has no bu ## Known Issues * JRuby/TruffleRuby might need `HOME` variable exposed. - See [`eamples/gem/.bazelrc`][7] to learn how to do that. + See [`examples/gem/.bazelrc`][7] to learn how to do that. This is to be fixed in [`jruby/jruby#5661`][9] and [`oracle/truffleruby#2784`][10]. * JRuby might fail with `Errno::EACCES: Permission denied - NUL` error on Windows. You need to configure JDK to allow proper access. - See [`examples/gem/.bazelrc`][7] to learn how to do that. This is described in [`jruby/jruby#7182`][11]. * RuboCop < 1.55 crashes with `LoadError` on Windows. This is fixed in [`rubocop/rubocop#12062`][12]. diff --git a/docs/repository_rules.md b/docs/repository_rules.md index 21b0a314..b34eae2d 100644 --- a/docs/repository_rules.md +++ b/docs/repository_rules.md @@ -2,6 +2,65 @@ Public API for repository rules + + +## rb_bundle_fetch + +
+rb_bundle_fetch(name, env, gemfile, gemfile_lock, repo_mapping, srcs)
+
+ + +Fetches Bundler dependencies to be automatically installed by other targets. + +Currently doesn't support installing gems from Git repositories, +see https://github.com/bazel-contrib/rules_ruby/issues/62. + +`WORKSPACE`: +```bazel +load("@rules_ruby//ruby:deps.bzl", "rb_bundle_fetch") + +rb_bundle_fetch( + name = "bundle", + gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", + srcs = [ + "//:gem.gemspec", + "//:lib/gem/version.rb", + ] +) +``` + +All the installed gems can be accessed using `@bundle` target and additionally +gems binary files can also be used: + +`BUILD`: +```bazel +load("@rules_ruby//ruby:defs.bzl", "rb_test") + +package(default_visibility = ["//:__subpackages__"]) + +rb_test( + name = "rubocop", + main = "@bundle//bin:rubocop", + deps = ["@bundle"], +) +``` + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this repository. | Name | required | | +| env | Environment variables to use during installation. | Dictionary: String -> String | optional | {} | +| gemfile | Gemfile to install dependencies from. | Label | required | | +| gemfile_lock | Gemfile.lock to install dependencies from. | Label | required | | +| repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.<p>For example, an entry "@foo": "@bar" declares that, for any time this repository depends on @foo (such as a dependency on @foo//some:target, it should actually resolve that dependency within globally-declared @bar (@bar//some:target). | Dictionary: String -> String | required | | +| srcs | List of Ruby source files necessary during installation. | List of labels | optional | [] | + + ## rb_bundle_rule @@ -11,6 +70,8 @@ rb_bundle_rule(name, toolchain, name, version, version_file, register, kwargs) +rb_register_toolchains(name, version, version_file, msys2_packages, register, kwargs) Register a Ruby toolchain and lazily download the Ruby Interpreter. @@ -91,7 +152,7 @@ rb_register_toolchains(name, name | base name of resulting repositories, by default "rules_ruby" | "ruby" | | version | a semver version of MRI, or a string like [interpreter type]-[version], or "system" | None | | version_file | .ruby-version or .tool-versions file to read version from | None | +| msys2_packages | extra MSYS2 packages to install | ["libyaml"] | | register | whether to register the resulting toolchains, should be False under bzlmod | True | | kwargs | additional parameters to the downloader for this interpreter type | none | diff --git a/docs/rules.md b/docs/rules.md index e6659cf1..29d0bc8a 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -123,7 +123,7 @@ package(default_visibility = ["//:__subpackages__"]) rb_binary( name = "rake", - main = "@bundle//:bin/rake", + main = "@bundle//bin:rake", deps = [ "//lib:gem", "@bundle", @@ -159,6 +159,58 @@ rake, version 10.5.0 | srcs | List of Ruby source files used to build the library. | List of labels | optional | [] | + + +## rb_bundle_install + +
+rb_bundle_install(name, env, gemfile, gemfile_lock, gems, srcs)
+
+ + +Installs Bundler dependencies from cached gems. + +You normally don't need to call this rule directly as it's an internal one +used by `rb_bundle_fetch()`. + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| env | Environment variables to use during installation. | Dictionary: String -> String | optional | {} | +| gemfile | Gemfile to install dependencies from. | Label | required | | +| gemfile_lock | Gemfile.lock to install dependencies from. | Label | required | | +| gems | List of gems in vendor/cache that are used to install dependencies from. | List of labels | required | | +| srcs | List of Ruby source files used to build the library. | List of labels | optional | [] | + + + + +## rb_gem + +
+rb_gem(name, gem)
+
+ + +Exposes a Ruby gem file. + +You normally don't need to call this rule directly as it's an internal one +used by `rb_bundle_fetch()`. + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| gem | Gem file. | Label | required | | + + ## rb_gem_build @@ -264,6 +316,74 @@ INFO: Build completed successfully, 2 total actions | srcs | List of Ruby source files used to build the library. | List of labels | optional | [] | + + +## rb_gem_install + +
+rb_gem_install(name, gem)
+
+ + +Installs a built Ruby gem. + +Suppose you have the following Ruby gem, where `rb_library()` is used +in `BUILD` files to define the packages for the gem and `rb_gem_build()` is used +to build a Ruby gem package from the sources. + +```output +|-- BUILD +|-- Gemfile +|-- WORKSPACE +|-- gem.gemspec +`-- lib + |-- BUILD + |-- gem + | |-- BUILD + | |-- add.rb + | |-- subtract.rb + | `-- version.rb + `-- gem.rb +``` + +You can now install the built `.gem` file by defining a target: + +`BUILD`: +```bazel +load("@rules_ruby//ruby:defs.bzl", "rb_gem_build", "rb_gem_install") + +package(default_visibility = ["//:__subpackages__"]) + +rb_gem_build( + name = "gem-build", + gemspec = "gem.gemspec", + deps = ["//lib:gem"], +) + +rb_gem_install( + name = "gem-install", + gem = ":gem-build", +) +``` + +```output +$ bazel build :gem-install +INFO: Analyzed target //:gem-install (4 packages loaded, 82 targets configured). +INFO: From Installing bazel-out/darwin_arm64-fastbuild/bin/gem-build.gem (//:gem-install): +Successfully installed example-0.1.0 +1 gem installed +``` + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| gem | Gem file to install. | Label | required | | + + ## rb_gem_push @@ -491,7 +611,7 @@ rb_test( name = "add", srcs = ["add_spec.rb"], args = ["spec/add_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [ ":spec_helper", "@bundle", @@ -502,7 +622,7 @@ rb_test( name = "subtract", srcs = ["subtract_spec.rb"], args = ["spec/subtract_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [ ":spec_helper", "@bundle", @@ -536,7 +656,7 @@ package(default_visibility = ["//:__subpackages__"]) rb_test( name = "rubocop", args = ["lib/"], - main = "@bundle//:bin/rubocop", + main = "@bundle//bin:rubocop", tags = ["no-sandbox"], deps = [ "//lib:gem", diff --git a/examples/gem/.bazelrc b/examples/gem/.bazelrc index 3d204603..7cdeaddf 100644 --- a/examples/gem/.bazelrc +++ b/examples/gem/.bazelrc @@ -198,11 +198,6 @@ test --test_verbose_timeout_warnings=false build --action_env=HOME test --test_env=HOME -# JRuby might fail with "Errno::EACCES: Permission denied - NUL" on Windows: -# https://github.com/jruby/jruby/issues/7182#issuecomment-1112953015. -build --action_env=JAVA_OPTS="-Djdk.io.File.enableADS=true" -test --test_env=JAVA_OPTS="-Djdk.io.File.enableADS=true" - # Allows to run tests with rdbg: # 1. Add breakpoint with `binding.break`. # 2. Run tests: `bazel test --config debug spec:add`. diff --git a/examples/gem/BUILD b/examples/gem/BUILD index 0ede04dd..4877deca 100644 --- a/examples/gem/BUILD +++ b/examples/gem/BUILD @@ -2,6 +2,7 @@ load( "@rules_ruby//ruby:defs.bzl", "rb_binary", "rb_gem_build", + "rb_gem_install", "rb_gem_push", "rb_test", ) @@ -10,7 +11,7 @@ package(default_visibility = ["//:__subpackages__"]) rb_binary( name = "rake", - main = "@bundle//:bin/rake", + main = "@bundle//bin:rake", deps = [ "//lib:gem", "@bundle", @@ -22,7 +23,7 @@ rb_test( size = "small", timeout = "moderate", # JRuby startup can be slow data = [".rubocop.yml"], - main = "@bundle//:bin/rubocop", + main = "@bundle//bin:rubocop", tags = ["no-sandbox"], deps = [ "//lib:gem", @@ -37,10 +38,12 @@ rb_test( rb_gem_build( name = "gem-build", gemspec = "gem.gemspec", - deps = [ - "//lib:gem", - "@bundle", - ], + deps = ["//lib:gem"], +) + +rb_gem_install( + name = "gem-install", + gem = ":gem-build", ) rb_gem_push( diff --git a/examples/gem/Gemfile.lock b/examples/gem/Gemfile.lock index e73785af..c58f5d87 100644 --- a/examples/gem/Gemfile.lock +++ b/examples/gem/Gemfile.lock @@ -86,4 +86,4 @@ DEPENDENCIES rubocop (~> 1.10, >= 1.55) BUNDLED WITH - 2.1.4 + 2.2.19 diff --git a/examples/gem/MODULE.bazel b/examples/gem/MODULE.bazel index 82faf805..02fdf11a 100644 --- a/examples/gem/MODULE.bazel +++ b/examples/gem/MODULE.bazel @@ -12,11 +12,9 @@ ruby.toolchain( name = "ruby", version_file = "//:.ruby-version", ) -use_repo(ruby, "ruby") -ruby.bundle( +ruby.bundle_fetch( name = "bundle", srcs = [ - "//:Gemfile.lock", "//:gem.gemspec", "//:lib/gem/version.rb", ], @@ -24,7 +22,7 @@ ruby.bundle( "BUNDLE_BUILD__FOO": "bar", }, gemfile = "//:Gemfile", - toolchain = "@ruby//:BUILD", + gemfile_lock = "//:Gemfile.lock", ) use_repo(ruby, "bundle", "ruby_toolchains") diff --git a/examples/gem/WORKSPACE b/examples/gem/WORKSPACE index 12e19385..f3e59301 100644 --- a/examples/gem/WORKSPACE +++ b/examples/gem/WORKSPACE @@ -13,16 +13,15 @@ http_archive( ], ) -load("@rules_ruby//ruby:deps.bzl", "rb_bundle", "rb_register_toolchains") +load("@rules_ruby//ruby:deps.bzl", "rb_bundle_fetch", "rb_register_toolchains") rb_register_toolchains( version_file = "//:.ruby-version", ) -rb_bundle( +rb_bundle_fetch( name = "bundle", srcs = [ - "//:Gemfile.lock", "//:gem.gemspec", "//:lib/gem/version.rb", ], @@ -30,4 +29,5 @@ rb_bundle( "BUNDLE_BUILD__FOO": "bar", }, gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", ) diff --git a/examples/gem/spec/BUILD b/examples/gem/spec/BUILD index 78b0614a..c09c9f38 100644 --- a/examples/gem/spec/BUILD +++ b/examples/gem/spec/BUILD @@ -17,7 +17,7 @@ rb_test( timeout = "moderate", # JRuby startup can be slow srcs = ["add_spec.rb"], args = ["spec/add_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [":spec_helper"], ) @@ -32,7 +32,7 @@ rb_test( "USER", # POSIX "USERNAME", # Windows ], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [":spec_helper"], ) @@ -43,7 +43,7 @@ rb_test( srcs = ["file_spec.rb"], args = ["spec/file_spec.rb"], data = ["support/file.txt"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [":spec_helper"], ) @@ -53,6 +53,6 @@ rb_test( timeout = "moderate", # JRuby startup can be slow srcs = ["subtract_spec.rb"], args = ["spec/subtract_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [":spec_helper"], ) diff --git a/examples/gem/spec/env_spec.rb b/examples/gem/spec/env_spec.rb index add838f9..442f8b10 100644 --- a/examples/gem/spec/env_spec.rb +++ b/examples/gem/spec/env_spec.rb @@ -10,4 +10,8 @@ specify do expect(ENV).to have_key('USER').or have_key('USERNAME') end + + specify do + expect(ENV.fetch('BUNDLE_BUILD__FOO')).to eq('bar') + end end diff --git a/ruby/BUILD b/ruby/BUILD index 2b0d9ea1..606e1897 100644 --- a/ruby/BUILD +++ b/ruby/BUILD @@ -14,7 +14,10 @@ bzl_library( srcs = ["defs.bzl"], deps = [ "//ruby/private:binary", + "//ruby/private:bundle_install", + "//ruby/private:gem", "//ruby/private:gem_build", + "//ruby/private:gem_install", "//ruby/private:gem_push", "//ruby/private:library", "//ruby/private:test", @@ -26,6 +29,7 @@ bzl_library( srcs = ["deps.bzl"], deps = [ "//ruby/private:bundle", + "//ruby/private:bundle_fetch", "//ruby/private:toolchain", ], ) diff --git a/ruby/defs.bzl b/ruby/defs.bzl index 28ac9c75..35515542 100644 --- a/ruby/defs.bzl +++ b/ruby/defs.bzl @@ -1,13 +1,19 @@ "Public API for rules" load("//ruby/private:binary.bzl", _rb_binary = "rb_binary") +load("//ruby/private:bundle_install.bzl", _rb_bundle_install = "rb_bundle_install") +load("//ruby/private:gem.bzl", _rb_gem = "rb_gem") load("//ruby/private:gem_build.bzl", _rb_gem_build = "rb_gem_build") +load("//ruby/private:gem_install.bzl", _rb_gem_install = "rb_gem_install") load("//ruby/private:gem_push.bzl", _rb_gem_push = "rb_gem_push") load("//ruby/private:library.bzl", _rb_library = "rb_library") load("//ruby/private:test.bzl", _rb_test = "rb_test") rb_binary = _rb_binary +rb_bundle_install = _rb_bundle_install +rb_gem = _rb_gem rb_gem_build = _rb_gem_build +rb_gem_install = _rb_gem_install rb_gem_push = _rb_gem_push rb_library = _rb_library rb_test = _rb_test diff --git a/ruby/deps.bzl b/ruby/deps.bzl index c48ac209..7b80bdef 100644 --- a/ruby/deps.bzl +++ b/ruby/deps.bzl @@ -1,6 +1,7 @@ "Public API for repository rules" load("//ruby/private:bundle.bzl", _rb_bundle = "rb_bundle") +load("//ruby/private:bundle_fetch.bzl", _rb_bundle_fetch = "rb_bundle_fetch") load("//ruby/private:toolchain.bzl", _rb_register_toolchains = "rb_register_toolchains") def rb_bundle(toolchain = "@ruby//:BUILD", **kwargs): @@ -17,4 +18,5 @@ def rb_bundle(toolchain = "@ruby//:BUILD", **kwargs): ) rb_register_toolchains = _rb_register_toolchains +rb_bundle_fetch = _rb_bundle_fetch rb_bundle_rule = _rb_bundle diff --git a/ruby/extensions.bzl b/ruby/extensions.bzl index 95ce26e8..9f63d6c7 100644 --- a/ruby/extensions.bzl +++ b/ruby/extensions.bzl @@ -1,7 +1,7 @@ "Module extensions used by bzlmod" load("//ruby/private:toolchain.bzl", "DEFAULT_RUBY_REPOSITORY") -load(":deps.bzl", "rb_bundle", "rb_register_toolchains") +load(":deps.bzl", "rb_bundle", "rb_bundle_fetch", "rb_register_toolchains") ruby_bundle = tag_class(attrs = { "name": attr.string(doc = "Resulting repository name for the bundle"), @@ -11,10 +11,19 @@ ruby_bundle = tag_class(attrs = { "toolchain": attr.label(), }) +ruby_bundle_fetch = tag_class(attrs = { + "name": attr.string(doc = "Resulting repository name for the bundle"), + "srcs": attr.label_list(), + "env": attr.string_dict(), + "gemfile": attr.label(), + "gemfile_lock": attr.label(), +}) + ruby_toolchain = tag_class(attrs = { "name": attr.string(doc = "Base name for generated repositories, allowing multiple to be registered."), "version": attr.string(doc = "Explicit version of ruby."), "version_file": attr.label(doc = "File to read Ruby version from."), + "msys2_packages": attr.string_list(doc = "Extra MSYS2 packages to install.", default = ["libyaml"]), }) def _ruby_module_extension(module_ctx): @@ -29,6 +38,15 @@ def _ruby_module_extension(module_ctx): toolchain = bundle.toolchain, ) + for bundle_fetch in mod.tags.bundle_fetch: + rb_bundle_fetch( + name = bundle_fetch.name, + srcs = bundle_fetch.srcs, + env = bundle_fetch.env, + gemfile = bundle_fetch.gemfile, + gemfile_lock = bundle_fetch.gemfile_lock, + ) + for toolchain in mod.tags.toolchain: # Prevent a users dependencies creating conflicting toolchain names if toolchain.name != DEFAULT_RUBY_REPOSITORY and not mod.is_root: @@ -38,20 +56,21 @@ def _ruby_module_extension(module_ctx): if toolchain.version == registrations[toolchain.name]: # No problem to register a matching toolchain twice continue - fail("Multiple conflicting toolchains declared for name {} ({} and {}".format( + fail("Multiple conflicting toolchains declared for name {} ({}, {}) and {}".format( toolchain.name, toolchain.version, toolchain.version_file, registrations[toolchain.name], )) else: - registrations[toolchain.name] = (toolchain.version, toolchain.version_file) + registrations[toolchain.name] = (toolchain.version, toolchain.version_file, toolchain.msys2_packages) - for name, (version, version_file) in registrations.items(): + for name, (version, version_file, msys2_packages) in registrations.items(): rb_register_toolchains( name = name, version = version, version_file = version_file, + msys2_packages = msys2_packages, register = False, ) @@ -59,6 +78,7 @@ ruby = module_extension( implementation = _ruby_module_extension, tag_classes = { "bundle": ruby_bundle, + "bundle_fetch": ruby_bundle_fetch, "toolchain": ruby_toolchain, }, ) diff --git a/ruby/private/BUILD b/ruby/private/BUILD index bc32b1ec..5dd6b254 100644 --- a/ruby/private/BUILD +++ b/ruby/private/BUILD @@ -1,7 +1,5 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") -exports_files(["gem_build/gem_builder.rb.tpl"]) - bzl_library( name = "binary", srcs = ["binary.bzl"], @@ -9,7 +7,7 @@ bzl_library( deps = [ ":library", ":providers", - "//ruby/private/binary:rlocation", + ":utils", ], ) @@ -20,6 +18,7 @@ bzl_library( deps = [ ":library", ":providers", + ":utils", ], ) @@ -60,6 +59,44 @@ bzl_library( ], ) +bzl_library( + name = "bundle_fetch", + srcs = ["bundle_fetch.bzl"], + visibility = ["//ruby:__subpackages__"], + deps = [ + ":utils", + "//ruby/private/bundle_fetch:gemfile_lock_parser", + ], +) + +bzl_library( + name = "bundle_install", + srcs = ["bundle_install.bzl"], + visibility = ["//ruby:__subpackages__"], + deps = [ + ":gem_install", + ":providers", + ":utils", + ], +) + +bzl_library( + name = "gem", + srcs = ["gem.bzl"], + visibility = ["//ruby:__subpackages__"], + deps = [":providers"], +) + +bzl_library( + name = "gem_install", + srcs = ["gem_install.bzl"], + visibility = ["//ruby:__subpackages__"], + deps = [ + ":providers", + ":utils", + ], +) + bzl_library( name = "bundle", srcs = ["bundle.bzl"], @@ -77,3 +114,9 @@ bzl_library( srcs = ["providers.bzl"], visibility = ["//ruby:__subpackages__"], ) + +bzl_library( + name = "utils", + srcs = ["utils.bzl"], + visibility = ["//ruby:__subpackages__"], +) diff --git a/ruby/private/binary.bzl b/ruby/private/binary.bzl index 4cd69dca..640ef8a7 100644 --- a/ruby/private/binary.bzl +++ b/ruby/private/binary.bzl @@ -3,6 +3,7 @@ load("//ruby/private:library.bzl", LIBRARY_ATTRS = "ATTRS") load( "//ruby/private:providers.bzl", + "BundlerInfo", "RubyFilesInfo", "get_bundle_env", "get_transitive_data", @@ -10,7 +11,14 @@ load( "get_transitive_runfiles", "get_transitive_srcs", ) -load("//ruby/private/binary:rlocation.bzl", "BASH_RLOCATION_FUNCTION", "BATCH_RLOCATION_FUNCTION") +load( + "//ruby/private:utils.bzl", + "BASH_RLOCATION_FUNCTION", + "BATCH_RLOCATION_FUNCTION", + _convert_env_to_script = "convert_env_to_script", + _is_windows = "is_windows", + _normalize_path = "normalize_path", +) ATTRS = { "main": attr.label( @@ -47,34 +55,27 @@ Use a built-in `args` attribute to pass extra arguments to the script. ), } -_EXPORT_ENV_VAR_COMMAND = "{command} {name}={value}" -_EXPORT_BATCH_COMMAND = "set" -_EXPORT_BASH_COMMAND = "export" - # buildifier: disable=function-docstring def generate_rb_binary_script(ctx, binary, bundler = False, args = [], env = {}, java_bin = ""): - windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo] - is_windows = ctx.target_platform_has_constraint(windows_constraint) toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] - toolchain_bindir = toolchain.bindir if binary: binary_path = binary.short_path else: binary_path = "" - if is_windows: - binary_path = binary_path.replace("/", "\\") - export_command = _EXPORT_BATCH_COMMAND + environment = {} + environment.update(env) + if _is_windows(ctx): rlocation_function = BATCH_RLOCATION_FUNCTION script = ctx.actions.declare_file("{}.rb.cmd".format(ctx.label.name)) - toolchain_bindir = toolchain_bindir.replace("/", "\\") template = ctx.file._binary_cmd_tpl + environment.update({"PATH": _normalize_path(ctx, toolchain.bindir) + ";%PATH%"}) else: - export_command = _EXPORT_BASH_COMMAND rlocation_function = BASH_RLOCATION_FUNCTION script = ctx.actions.declare_file("{}.rb.sh".format(ctx.label.name)) template = ctx.file._binary_sh_tpl + environment.update({"PATH": "%s:$PATH" % toolchain.bindir}) if bundler: bundler_command = "bundle exec" @@ -84,20 +85,14 @@ def generate_rb_binary_script(ctx, binary, bundler = False, args = [], env = {}, args = " ".join(args) args = ctx.expand_location(args) - environment = [] - for (name, value) in env.items(): - command = _EXPORT_ENV_VAR_COMMAND.format(command = export_command, name = name, value = value) - environment.append(command) - ctx.actions.expand_template( template = template, output = script, is_executable = True, substitutions = { "{args}": args, - "{binary}": binary_path, - "{toolchain_bindir}": toolchain_bindir, - "{env}": "\n".join(environment), + "{binary}": _normalize_path(ctx, binary_path), + "{env}": _convert_env_to_script(ctx, environment), "{bundler_command}": bundler_command, "{ruby_binary_name}": toolchain.ruby.basename, "{java_bin}": java_bin, @@ -119,23 +114,37 @@ def rb_binary_impl(ctx): transitive_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps).to_list() ruby_toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] - tools = [ruby_toolchain.ruby, ruby_toolchain.bundle, ruby_toolchain.gem] + tools = [ruby_toolchain.ruby, ruby_toolchain.bundle, ruby_toolchain.gem, ctx.file._runfiles_library] if ruby_toolchain.version.startswith("jruby"): java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] - tools.extend(ctx.files._runfiles_library) tools.extend(java_toolchain.java_runtime.files.to_list()) java_bin = java_toolchain.java_runtime.java_executable_runfiles_path[3:] for dep in transitive_deps: - # TODO: Do not depend on workspace name to determine bundle + # TODO: Remove workspace name check together with `rb_bundle()` if dep.label.workspace_name.endswith("bundle"): bundler = True + if BundlerInfo in dep: + info = dep[BundlerInfo] + transitive_srcs.extend([info.gemfile, info.bin, info.path]) + bundler = True + + # See https://bundler.io/v2.5/man/bundle-config.1.html for confiugration keys. + env.update({ + "BUNDLE_GEMFILE": info.gemfile.short_path.removeprefix("../"), + "BUNDLE_PATH": info.path.short_path.removeprefix("../"), + }) + bundle_env = get_bundle_env(ctx.attr.env, ctx.attr.deps) env.update(bundle_env) + env.update(ruby_toolchain.env) env.update(ctx.attr.env) + runfiles = ctx.runfiles(transitive_srcs + transitive_data + tools) + runfiles = get_transitive_runfiles(runfiles, ctx.attr.srcs, ctx.attr.deps, ctx.attr.data) + script = generate_rb_binary_script( ctx, ctx.executable.main, @@ -144,9 +153,6 @@ def rb_binary_impl(ctx): java_bin = java_bin, ) - runfiles = ctx.runfiles(transitive_srcs + transitive_data + tools) - runfiles = get_transitive_runfiles(runfiles, ctx.attr.srcs, ctx.attr.deps, ctx.attr.data) - return [ DefaultInfo( executable = script, @@ -291,7 +297,7 @@ package(default_visibility = ["//:__subpackages__"]) rb_binary( name = "rake", - main = "@bundle//:bin/rake", + main = "@bundle//bin:rake", deps = [ "//lib:gem", "@bundle", diff --git a/ruby/private/binary/BUILD b/ruby/private/binary/BUILD index 4f09aefc..0bb1cb2e 100644 --- a/ruby/private/binary/BUILD +++ b/ruby/private/binary/BUILD @@ -1,9 +1,4 @@ -load("@bazel_skylib//:bzl_library.bzl", "bzl_library") - -exports_files(glob(["*.tpl"])) - -bzl_library( - name = "rlocation", - srcs = ["rlocation.bzl"], - visibility = ["//ruby:__subpackages__"], -) +exports_files([ + "binary.cmd.tpl", + "binary.sh.tpl", +]) diff --git a/ruby/private/binary/binary.cmd.tpl b/ruby/private/binary/binary.cmd.tpl index 98a7f4c8..a4c4b41d 100644 --- a/ruby/private/binary/binary.cmd.tpl +++ b/ruby/private/binary/binary.cmd.tpl @@ -1,16 +1,23 @@ @echo off setlocal enableextensions enabledelayedexpansion +set RUNFILES_MANIFEST_ONLY=1 +{rlocation_function} + :: Find location of JAVA_HOME in runfiles. if "{java_bin}" neq "" ( - {rlocation_function} - set RUNFILES_MANIFEST_ONLY=1 call :rlocation {java_bin} java_bin - for %%a in ("%java_bin%\..\..") do set JAVA_HOME=%%~fa + for %%a in ("!java_bin!\..\..") do set JAVA_HOME=%%~fa ) :: Set environment variables. -set PATH={toolchain_bindir};%PATH% {env} +if "{bundler_command}" neq "" ( + call :rlocation "!BUNDLE_GEMFILE!" BUNDLE_GEMFILE + call :rlocation "!BUNDLE_PATH!" BUNDLE_PATH +) + {bundler_command} {ruby_binary_name} {binary} {args} %* + +:: vim: ft=dosbatch diff --git a/ruby/private/binary/binary.sh.tpl b/ruby/private/binary/binary.sh.tpl index ec323110..25f1cd64 100644 --- a/ruby/private/binary/binary.sh.tpl +++ b/ruby/private/binary/binary.sh.tpl @@ -1,13 +1,36 @@ #!/usr/bin/env bash +{rlocation_function} + +# Provide a realpath implementation for macOS. +realpath() ( + OURPWD=$PWD + cd "$(dirname "$1")" + LINK=$(readlink "$(basename "$1")") + while [ "$LINK" ]; do + cd "$(dirname "$LINK")" + LINK=$(readlink "$(basename "$1")") + done + REALPATH="$PWD/$(basename "$1")" + cd "$OURPWD" + echo "$REALPATH" +) + +export RUNFILES_DIR="$(realpath "${RUNFILES_DIR:-$0.runfiles}")" + # Find location of JAVA_HOME in runfiles. if [ -n "{java_bin}" ]; then - {rlocation_function} export JAVA_HOME=$(dirname $(dirname $(rlocation "{java_bin}"))) fi # Set environment variables. -export PATH={toolchain_bindir}:$PATH {env} -{bundler_command} {ruby_binary_name} {binary} {args} $@ +if [ -n "{bundler_command}" ]; then + export BUNDLE_GEMFILE=$(rlocation $BUNDLE_GEMFILE) + export BUNDLE_PATH=$(rlocation $BUNDLE_PATH) +fi + +{bundler_command} {ruby_binary_name} {binary} {args} "$@" + +# vim: ft=bash diff --git a/ruby/private/bundle.bzl b/ruby/private/bundle.bzl index 5d65fb68..af119b52 100644 --- a/ruby/private/bundle.bzl +++ b/ruby/private/bundle.bzl @@ -6,7 +6,16 @@ _BINSTUB_CMD = """@ruby -x "%~f0" %* {} """ +_DEPRECATED_MESSAGE = """ + +rb_bundle(...) is deprecated and will be removed in the future versions. +rb_bundle_fetch(...) should be used instead. + +""" + def _rb_bundle_impl(repository_ctx): + print(_DEPRECATED_MESSAGE) # buildifier: disable=print + binstubs_path = repository_ctx.path("bin") bundle_path = repository_ctx.path(".") gemfile_path = repository_ctx.path(repository_ctx.attr.gemfile) @@ -96,6 +105,8 @@ Gemfile to install dependencies from. ), }, doc = """ +(Deprecated) Use `rb_bundle_fetch()` instead. + Installs Bundler dependencies and registers an external repository that can be used by other targets. diff --git a/ruby/private/bundle/BUILD.tpl b/ruby/private/bundle/BUILD.tpl index 7f3c5a1d..b1ccc575 100644 --- a/ruby/private/bundle/BUILD.tpl +++ b/ruby/private/bundle/BUILD.tpl @@ -1,4 +1,5 @@ """@generated by @rules_ruby//:ruby/private/binary.bzl""" + load("@rules_ruby//ruby:defs.bzl", "rb_library") load("//:defs.bzl", "BUNDLE_ENV") @@ -6,6 +7,8 @@ package(default_visibility = ["//visibility:public"]) rb_library( name = "bundle", - data = glob(["**/*"]), bundle_env = BUNDLE_ENV, + data = glob(["**/*"]), ) + +# vim: ft=bzl diff --git a/ruby/private/bundle_fetch.bzl b/ruby/private/bundle_fetch.bzl new file mode 100644 index 00000000..3f7b40b8 --- /dev/null +++ b/ruby/private/bundle_fetch.bzl @@ -0,0 +1,228 @@ +"Implementation details for fetch the bundler" + +load( + "//ruby/private:utils.bzl", + _join_and_indent = "join_and_indent", + _normalize_bzlmod_repository_name = "normalize_bzlmod_repository_name", +) +load("//ruby/private/bundle_fetch:gemfile_lock_parser.bzl", "parse_gemfile_lock") + +_GEM_BUILD_FRAGMENT = """ +rb_gem( + name = "{name}", + gem = "{cache_path}/{gem}", +) +""" + +_GEM_INSTALL_BUILD_FRAGMENT = """ +rb_gem_install( + name = "{name}", + gem = "{cache_path}/{gem}", +) +""" + +_GIT_UNSUPPORTED_ERROR = """ + +rb_bundle_fetch(...) does not support gems installed from Git yet. +See https://github.com/bazel-contrib/rules_ruby/issues/62 for more details. + +""" + +_OUTDATED_BUNDLER_ERROR = """ + +rb_bundle_fetch(...) requires Bundler 2.2.19 or later in Gemfile.lock. +Please update Bundler version and try again. +See https://github.com/rubygems/rubygems/issues/4620 for more details. + +""" + +def _is_outdated_bundler(version): + """Checks that Bundler version is 2.2.19+. + + Older versions don't work with cached gems properly. + See https://github.com/rubygems/rubygems/issues/4620 for more details. + """ + major, minor, patch = version.split(".") + return (int(major) < 2 or int(minor) < 2 or int(patch) < 19) + +def _download_gem(repository_ctx, gem, cache_path): + """Downloads gem into a predefined vendor/cache location.""" + url = "{remote}gems/{filename}".format(remote = gem.remote, filename = gem.filename) + repository_ctx.download(url = url, output = "%s/%s" % (cache_path, gem.filename)) + +def _get_gem_executables(repository_ctx, gem, cache_path): + """Unpacks downloaded gem and returns its executables. + + Ideally, we would read the list of executables from gem metadata.gz, + which is a compressed YAML file. It has a separate `executables` field + containing the exact list of files. However, Bazel cannot decompress `.gz` + files, so we would need to use an external tool such as `gzcat` or `busybox`. + The tool should also work on all OSes. For now, a simpler path is taken + where we unpack the gem completely and then try to determin executables + by looking into its `bin` and `exe` locations. This is accurate enough so far, + so some exotic gems might not work correctly. + """ + executables = [] + repository_ctx.symlink(cache_path + "/" + gem.filename, gem.filename + ".tar") + repository_ctx.extract(gem.filename + ".tar", output = gem.full_name) + data = "/".join([gem.full_name, "data"]) + repository_ctx.extract("/".join([gem.full_name, "data.tar.gz"]), output = data) + gem_contents = repository_ctx.path(data) + + executable_dirnames = ["bin", "exe"] + for executable_dirname in executable_dirnames: + if gem_contents.get_child(executable_dirname).exists: + for executable in gem_contents.get_child(executable_dirname).readdir(): + executables.append(executable.basename) + + _cleanup_downloads(repository_ctx, gem) + return executables + +def _cleanup_downloads(repository_ctx, gem): + """Removes unnecessary downloaded/unpacked files.""" + repository_ctx.delete(gem.full_name) + repository_ctx.delete(gem.filename + ".tar") + +def _rb_bundle_fetch_impl(repository_ctx): + # Define vendor/cache relative to the location of Gemfile. + # This is expected by Bundler to operate correctly. + gemfile_dir = repository_ctx.attr.gemfile.name.rpartition("/")[0] + cache_path = ("%s/vendor/cache" % gemfile_dir).removeprefix("/") + + # Copy all necessary inputs to the repository. + gemfile_path = repository_ctx.path(repository_ctx.attr.gemfile) + gemfile_lock_path = repository_ctx.path(repository_ctx.attr.gemfile_lock) + repository_ctx.file(repository_ctx.attr.gemfile.name, repository_ctx.read(gemfile_path)) + repository_ctx.file(repository_ctx.attr.gemfile_lock.name, repository_ctx.read(gemfile_lock_path)) + srcs = [] + for src in repository_ctx.attr.srcs: + srcs.append(src.name) + repository_ctx.file(src.name, repository_ctx.read(src)) + + gemfile_lock = parse_gemfile_lock(repository_ctx.read(gemfile_lock_path)) + if _is_outdated_bundler(gemfile_lock.bundler.version): + fail(_OUTDATED_BUNDLER_ERROR) + + if len(gemfile_lock.git_packages) > 0: + fail(_GIT_UNSUPPORTED_ERROR) + + executables = [] + gem_full_names = [] + gem_fragments = [] + gem_install_fragments = [] + + # Fetch gems and expose them as `rb_gem()` targets. + for gem in gemfile_lock.remote_packages: + _download_gem(repository_ctx, gem, cache_path) + executables.extend(_get_gem_executables(repository_ctx, gem, cache_path)) + gem_full_names.append(":%s" % gem.full_name) + gem_fragments.append(_GEM_BUILD_FRAGMENT.format(name = gem.full_name, gem = gem.filename, cache_path = cache_path)) + + # Fetch Bundler and define an `rb_gem_install()` target for it. + _download_gem(repository_ctx, gemfile_lock.bundler, cache_path) + executables.extend(_get_gem_executables(repository_ctx, gemfile_lock.bundler, cache_path)) + gem_full_names.append(":%s" % gemfile_lock.bundler.full_name) + gem_install_fragments.append( + _GEM_INSTALL_BUILD_FRAGMENT.format( + name = gemfile_lock.bundler.full_name, + gem = gemfile_lock.bundler.filename, + cache_path = cache_path, + ), + ) + + repository_ctx.template( + "BUILD", + repository_ctx.attr._build_tpl, + executable = False, + substitutions = { + "{name}": _normalize_bzlmod_repository_name(repository_ctx.name), + "{srcs}": _join_and_indent(srcs), + "{gemfile_path}": repository_ctx.attr.gemfile.name, + "{gemfile_lock_path}": repository_ctx.attr.gemfile_lock.name, + "{gems}": _join_and_indent(gem_full_names), + "{gem_fragments}": "".join(gem_fragments), + "{gem_install_fragments}": "".join(gem_install_fragments), + "{env}": repr(repository_ctx.attr.env), + }, + ) + + # Create `bin` package with shims for gem executables. + # This allows targets to depend on `@bundle//bin:rake`. + repository_ctx.template( + "bin/BUILD", + repository_ctx.attr._bin_build_tpl, + executable = False, + substitutions = { + "{name}": _normalize_bzlmod_repository_name(repository_ctx.name), + }, + ) + for executable in executables: + repository_ctx.file("bin/%s" % executable) + +rb_bundle_fetch = repository_rule( + implementation = _rb_bundle_fetch_impl, + attrs = { + "gemfile": attr.label( + allow_single_file = ["Gemfile"], + mandatory = True, + doc = "Gemfile to install dependencies from.", + ), + "gemfile_lock": attr.label( + allow_single_file = ["Gemfile.lock"], + mandatory = True, + doc = "Gemfile.lock to install dependencies from.", + ), + "srcs": attr.label_list( + allow_files = True, + doc = "List of Ruby source files necessary during installation.", + ), + "env": attr.string_dict( + doc = "Environment variables to use during installation.", + ), + "_build_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//:ruby/private/bundle_fetch/BUILD.tpl", + ), + "_bin_build_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//:ruby/private/bundle_fetch/bin/BUILD.tpl", + ), + }, + doc = """ +Fetches Bundler dependencies to be automatically installed by other targets. + +Currently doesn't support installing gems from Git repositories, +see https://github.com/bazel-contrib/rules_ruby/issues/62. + +`WORKSPACE`: +```bazel +load("@rules_ruby//ruby:deps.bzl", "rb_bundle_fetch") + +rb_bundle_fetch( + name = "bundle", + gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", + srcs = [ + "//:gem.gemspec", + "//:lib/gem/version.rb", + ] +) +``` + +All the installed gems can be accessed using `@bundle` target and additionally +gems binary files can also be used: + +`BUILD`: +```bazel +load("@rules_ruby//ruby:defs.bzl", "rb_test") + +package(default_visibility = ["//:__subpackages__"]) + +rb_test( + name = "rubocop", + main = "@bundle//bin:rubocop", + deps = ["@bundle"], +) +``` + """, +) diff --git a/ruby/private/bundle_fetch/BUILD b/ruby/private/bundle_fetch/BUILD new file mode 100644 index 00000000..38556333 --- /dev/null +++ b/ruby/private/bundle_fetch/BUILD @@ -0,0 +1,7 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "gemfile_lock_parser", + srcs = ["gemfile_lock_parser.bzl"], + visibility = ["//ruby:__subpackages__"], +) diff --git a/ruby/private/bundle_fetch/BUILD.tpl b/ruby/private/bundle_fetch/BUILD.tpl new file mode 100644 index 00000000..9ff35853 --- /dev/null +++ b/ruby/private/bundle_fetch/BUILD.tpl @@ -0,0 +1,20 @@ +"""@generated by @rules_ruby//:ruby/private/bundle_fetch.bzl""" + +load("@rules_ruby//ruby:defs.bzl", "rb_bundle_install", "rb_gem", "rb_gem_install") + +package(default_visibility = ["//visibility:public"]) + +rb_bundle_install( + name = "{name}", + srcs = {srcs}, + env = {env}, + gemfile = "{gemfile_path}", + gemfile_lock = "{gemfile_lock_path}", + gems = {gems}, +) + +{gem_fragments} + +{gem_install_fragments} + +# vim: ft=bzl diff --git a/ruby/private/bundle_fetch/bin/BUILD.tpl b/ruby/private/bundle_fetch/bin/BUILD.tpl new file mode 100644 index 00000000..cc21f406 --- /dev/null +++ b/ruby/private/bundle_fetch/bin/BUILD.tpl @@ -0,0 +1,13 @@ +"""@generated by @rules_ruby//:ruby/private/bundle_fetch.bzl""" + +load("@rules_ruby//ruby:defs.bzl", "rb_library") + +package(default_visibility = ["//visibility:public"]) + +rb_library( + name = "bin", + data = glob(["*"]), + deps = ["//:{name}"], +) + +# vim: ft=bzl diff --git a/ruby/private/bundle_fetch/gemfile_lock_parser.bzl b/ruby/private/bundle_fetch/gemfile_lock_parser.bzl new file mode 100644 index 00000000..7a17ef85 --- /dev/null +++ b/ruby/private/bundle_fetch/gemfile_lock_parser.bzl @@ -0,0 +1,191 @@ +""" +Parses a Gemfile.lock purely in Starlark. + +Largely based on https://github.com/sushain97/rules_ruby/blob/master/tools/ruby/gemfile_parser.bzl (private). +Modifications include: + - Support for parsing out the gem remote URL. + - Usage of structs with extra fields as return values. +""" + +def _parse_package(line, remote): + """Parses an exact package specification from a single line of a Gemfile.lock. + + The Gemfile.lock format uses two spaces for each level of indentation. The + lines that we're interested in are nested underneath the `GEM.specs` + section (the concrete gems required and their exact versions). + + The lines that are parsed out of the 'GEM.specs' section will have four + leading spaces, and consist of the package name and exact version needed + in parenthesis. + + > gem-name (gem-version) + + What's returned is a struct that has the following fields in this case: + + > struct( + > name = "gem-name", + > version = "gem-version", + > full_name = "gem-name-gem-version", + > filename = "gem-name-gem-version.gem", + > remote = "https://rubygems.org" + > ) + + If the line does not match that format, `None` is returned. + """ + + prefix = line[0:4] + if not prefix.isspace(): + return None + + suffix = line[4:] + if suffix[0].isspace(): + return None + + version_start = suffix.find(" (") + if version_start < 0: + return None + + package = suffix[0:version_start] + version = suffix[version_start + 2:-1] + + return struct( + name = package, + version = version, + filename = "%s-%s.gem" % (package, version), + full_name = "%s-%s" % (package, version), + remote = remote, + ) + +def _parse_top_section(line): + """Parse a top-level section name. + + Returns a top-level section name ("PATH", "GEM", "PLATFORMS", + "DEPENDENCIES", etc.), or `None` if the line is empty or contains leading + space. + """ + + if line == "" or line[0].isspace(): + return None + + return line + +def _parse_remote(line): + """Parse a remote URL for packages. + + An example line is: + > remote: https://rubygems.org/ + """ + prefix = " remote: " + if line.startswith(prefix): + return line.removeprefix(prefix).strip() + + return None + +def _parse_git_package(lines): + """Parse a Git specification from several lines of a Gemfile.lock. + + The relevant lines begin with either `remote` or `revision`. + + > remote: path:to/remote.git + > revision: rev + + What's returned is a dict that will have two fields: + + > { "revision": "rev", "remote": "path:to/remote.git" } + + in this case. + + If the line does not match that format, an error is raised. + """ + remote = None + revision = None + + for line in lines: + if "remote: " in line: + remote = line.split(":", 1)[1].strip() + elif "revision: " in line: + revision = line.split(":", 1)[1].strip() + + if revision == None or remote == None: + fail("Unable to parse git package from gemfile: {}. Found remote={}, revision={}.".format(lines, remote, revision)) + + return {"revision": revision, "remote": remote} + +def parse_gemfile_lock(content): + """Parses a Gemfile.lock. + + Find lines in the content of a Gemfile.lock that look like package + constraints. + + Args: + content: Gemfile.lock contents + + Returns: + struct with parsed Gemfile.lock + """ + + remote_packages = [] + git_packages = [] + bundler = None + remote = None + + inside_gem = False + inside_git = False + inside_bundled_with = False + + git_lines = [] + + for line in content.splitlines(): + top_section = _parse_top_section(line) + if top_section != None: + # Toggle gem specification parsing. + if top_section == "GEM": + inside_gem = True + remote = None + + # Toggle bundler version parsing. Skip to the next line which + # has the actual version. + inside_bundled_with = (top_section == "BUNDLED WITH") + if inside_bundled_with: + continue + + # Toggle git specification parsing. + inside_git = (top_section == "GIT") + + # Only parse gem specifications from the GEM section. + if inside_gem: + if remote: + info = _parse_package(line, remote) + if info != None: + remote_packages.append(info) + else: + remote = _parse_remote(line) + + # Only parse git specifications from the GIT section. + if inside_git: + if line == "": + # The git section is complete, parse its information. + git_packages.append(_parse_git_package(git_lines)) + git_lines = [] + inside_git = False + else: + # Buffer up the git section. + git_lines.append(line) + + # Only parse bundler version from the BUNDLED_WITH section. + if inside_bundled_with: + version = line.strip() + bundler = struct( + name = "bundler", + version = version, + filename = "bundler-%s.gem" % version, + full_name = "bundler-%s" % version, + remote = "https://rubygems.org/", + ) + inside_bundled_with = False + + return struct( + bundler = bundler, + git_packages = git_packages, + remote_packages = remote_packages, + ) diff --git a/ruby/private/bundle_install.bzl b/ruby/private/bundle_install.bzl new file mode 100644 index 00000000..5dbaf120 --- /dev/null +++ b/ruby/private/bundle_install.bzl @@ -0,0 +1,158 @@ +"Implementation details for rb_bundle_install" + +load("//ruby/private:providers.bzl", "BundlerInfo", "GemInfo", "RubyFilesInfo") +load( + "//ruby/private:utils.bzl", + _convert_env_to_script = "convert_env_to_script", + _is_windows = "is_windows", + _normalize_path = "normalize_path", +) + +def _rb_bundle_install_impl(ctx): + toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] + + tools = [toolchain.ruby, toolchain.bundle] + bundler_exe = toolchain.bundle.path + + for gem in ctx.attr.gems: + if gem[GemInfo].name == "bundler": + # Use Bundler version defined in Gemfile.lock. + full_name = "%s-%s" % (gem[GemInfo].name, gem[GemInfo].version) + bundler_exe = gem.files.to_list()[-1].path + "/gems/" + full_name + "/exe/bundle" + tools.extend(gem.files.to_list()) + + binstubs = ctx.actions.declare_directory("bin") + bundle_path = ctx.actions.declare_directory("vendor/bundle") + + env = {} + env.update(toolchain.env) + env.update(ctx.attr.env) + if toolchain.version.startswith("jruby"): + java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] + tools.extend(java_toolchain.java_runtime.files.to_list()) + env.update({"JAVA_HOME": java_toolchain.java_runtime.java_home}) + + if _is_windows(ctx): + script = ctx.actions.declare_file("bundle_install_{}.cmd".format(ctx.label.name)) + template = ctx.file._bundle_install_cmd_tpl + env.update({"PATH": _normalize_path(ctx, toolchain.bindir) + ";%PATH%"}) + else: + script = ctx.actions.declare_file("bundle_install_{}.sh".format(ctx.label.name)) + template = ctx.file._bundle_install_sh_tpl + env.update({"PATH": "%s:$PATH" % toolchain.bindir}) + + # Calculate relative location between BUNDLE_GEMFILE and BUNDLE_PATH. + relative_dir = "../../" + for _ in ctx.file.gemfile.short_path.split("/")[2:-1]: + relative_dir += "../" + + # See https://bundler.io/v2.5/man/bundle-config.1.html for confiugration keys. + env.update({ + "BUNDLE_BIN": "/".join([relative_dir, binstubs.path]), + "BUNDLE_DEPLOYMENT": "1", + "BUNDLE_DISABLE_SHARED_GEMS": "1", + "BUNDLE_DISABLE_VERSION_CHECK": "1", + "BUNDLE_GEMFILE": _normalize_path(ctx, ctx.file.gemfile.path), + "BUNDLE_IGNORE_CONFIG": "1", + "BUNDLE_PATH": _normalize_path(ctx, "/".join([relative_dir, bundle_path.path])), + "BUNDLE_SHEBANG": _normalize_path(ctx, toolchain.ruby.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), + }, + ) + + ctx.actions.run( + executable = script, + inputs = depset([ctx.file.gemfile, ctx.file.gemfile_lock] + ctx.files.srcs + ctx.files.gems), + outputs = [binstubs, bundle_path], + mnemonic = "BundleInstall", + progress_message = "Running bundle install (%{label})", + tools = tools, + use_default_shell_env = True, + ) + + files = [ + ctx.file.gemfile, + ctx.file.gemfile_lock, + binstubs, + bundle_path, + ] + ctx.files.srcs + + return [ + DefaultInfo( + files = depset(files), + runfiles = ctx.runfiles(files), + ), + RubyFilesInfo( + transitive_srcs = depset([ctx.file.gemfile, ctx.file.gemfile_lock] + ctx.files.srcs), + transitive_deps = depset(), + transitive_data = depset(), + bundle_env = {}, + ), + BundlerInfo( + bin = binstubs, + env = ctx.attr.env, + gemfile = ctx.file.gemfile, + path = bundle_path, + ), + ] + +rb_bundle_install = rule( + _rb_bundle_install_impl, + attrs = { + "gemfile": attr.label( + allow_single_file = ["Gemfile"], + mandatory = True, + doc = "Gemfile to install dependencies from.", + ), + "gemfile_lock": attr.label( + allow_single_file = ["Gemfile.lock"], + mandatory = True, + doc = "Gemfile.lock to install dependencies from.", + ), + "gems": attr.label_list( + allow_files = [".gem"], + mandatory = True, + doc = "List of gems in vendor/cache that are used to install dependencies from.", + ), + "srcs": attr.label_list( + allow_files = True, + doc = "List of Ruby source files used to build the library.", + ), + "env": attr.string_dict( + doc = "Environment variables to use during installation.", + ), + "_runfiles_library": attr.label( + allow_single_file = True, + default = "@bazel_tools//tools/bash/runfiles", + ), + "_bundle_install_sh_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//ruby/private/bundle_install:bundle_install.sh.tpl", + ), + "_bundle_install_cmd_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//ruby/private/bundle_install:bundle_install.cmd.tpl", + ), + "_windows_constraint": attr.label( + default = "@platforms//os:windows", + ), + }, + toolchains = [ + "@rules_ruby//ruby:toolchain_type", + "@bazel_tools//tools/jdk:runtime_toolchain_type", + ], + doc = """ +Installs Bundler dependencies from cached gems. + +You normally don't need to call this rule directly as it's an internal one +used by `rb_bundle_fetch()`. + """, +) diff --git a/ruby/private/bundle_install/BUILD b/ruby/private/bundle_install/BUILD new file mode 100644 index 00000000..61e222e4 --- /dev/null +++ b/ruby/private/bundle_install/BUILD @@ -0,0 +1,4 @@ +exports_files([ + "bundle_install.cmd.tpl", + "bundle_install.sh.tpl", +]) diff --git a/ruby/private/bundle_install/bundle_install.cmd.tpl b/ruby/private/bundle_install/bundle_install.cmd.tpl new file mode 100644 index 00000000..4439f72b --- /dev/null +++ b/ruby/private/bundle_install/bundle_install.cmd.tpl @@ -0,0 +1,7 @@ +@echo off + +{env} + +{ruby_path} {bundler_exe} install --local + +:: vim: ft=dosbatch diff --git a/ruby/private/bundle_install/bundle_install.sh.tpl b/ruby/private/bundle_install/bundle_install.sh.tpl new file mode 100644 index 00000000..a4290576 --- /dev/null +++ b/ruby/private/bundle_install/bundle_install.sh.tpl @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +{env} + +{ruby_path} {bundler_exe} install --local + +# vim: ft=bash diff --git a/ruby/private/download.bzl b/ruby/private/download.bzl index f23e03a1..76509df5 100644 --- a/ruby/private/download.bzl +++ b/ruby/private/download.bzl @@ -30,6 +30,16 @@ def _rb_download_impl(repository_ctx): ruby_binary_name = "ruby" gem_binary_name = "gem" + env = {} + if version.startswith("jruby"): + # JRuby might fail with "Errno::EACCES: Permission denied - NUL" on Windows: + # https://github.com/jruby/jruby/issues/7182#issuecomment-1112953015 + env.update({"JAVA_OPTS": "-Djdk.io.File.enableADS=true"}) + elif version.startswith("truffleruby"): + # TruffleRuby needs explicit locale + # https://www.graalvm.org/dev/reference-manual/ruby/UTF8Locale/ + env.update({"LANG": "en_US.UTF-8"}) + repository_ctx.template( "BUILD", repository_ctx.attr._build_tpl, @@ -39,6 +49,7 @@ def _rb_download_impl(repository_ctx): "{version}": version, "{ruby_binary_name}": ruby_binary_name, "{gem_binary_name}": gem_binary_name, + "{env}": repr(env), }, ) @@ -67,6 +78,7 @@ def _install_jruby(repository_ctx, version): 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") # 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): @@ -89,10 +101,28 @@ def _install_via_rubyinstaller(repository_ctx, version): if result.return_code != 0: fail("%s\n%s" % (result.stdout, result.stderr)) - result = repository_ctx.execute(["./dist/bin/ridk.cmd", "install", "1"]) + result = repository_ctx.execute(["./dist/bin/ridk.cmd", "install", "1", "3"]) if result.return_code != 0: fail("%s\n%s" % (result.stdout, result.stderr)) + if len(repository_ctx.attr.msys2_packages) > 0: + mingw_package_prefix = None + result = repository_ctx.execute([ + "./dist/bin/ruby.exe", + "-rruby_installer", + "-e", + "puts RubyInstaller::Runtime::Msys2Installation.new.mingw_package_prefix", + ]) + if result.return_code != 0: + fail("%s\n%s" % (result.stdout, result.stderr)) + else: + mingw_package_prefix = result.stdout.strip() + + packages = ["%s-%s" % (mingw_package_prefix, package) for package in repository_ctx.attr.msys2_packages] + result = repository_ctx.execute(["./dist/bin/ridk.cmd", "exec", "pacman", "--sync", "--noconfirm"] + packages) + if result.return_code != 0: + fail("%s\n%s" % (result.stdout, result.stderr)) + binpath = repository_ctx.path("dist/bin") if not binpath.get_child("bundle.cmd").exists: repository_ctx.symlink( @@ -136,6 +166,14 @@ rb_download = repository_rule( allow_single_file = [".ruby-version"], doc = "File to read Ruby version from.", ), + "msys2_packages": attr.string_list( + default = ["libyaml"], + doc = """ +Extra MSYS2 packages to install. + +By default, contains `libyaml` (dependency of a `psych` gem). +""", + ), "ruby_build_version": attr.string( default = "20231225", doc = """ diff --git a/ruby/private/download/BUILD.tpl b/ruby/private/download/BUILD.tpl index d43abf69..eefaa786 100644 --- a/ruby/private/download/BUILD.tpl +++ b/ruby/private/download/BUILD.tpl @@ -20,14 +20,20 @@ filegroup( filegroup( name = "gem", - srcs = ["dist/bin/{gem_binary_name}"], + srcs = select({ + "@platforms//os:windows": ["dist/bin/{gem_binary_name}.cmd"], + "//conditions:default": ["dist/bin/{gem_binary_name}"], + }), ) rb_toolchain( name = "toolchain", - ruby = ":ruby", + bindir = "{bindir}", bundle = ":bundle", + env = {env}, gem = ":gem", - bindir = "{bindir}", + ruby = ":ruby", version = "{version}", ) + +# vim: ft=bzl diff --git a/ruby/private/gem.bzl b/ruby/private/gem.bzl new file mode 100644 index 00000000..5f580ccd --- /dev/null +++ b/ruby/private/gem.bzl @@ -0,0 +1,32 @@ +"Implementation details for rb_gem" + +load("//ruby/private:providers.bzl", "GemInfo") + +def _rb_gem_impl(ctx): + gem = ctx.file.gem + name, _, version = ctx.attr.name.rpartition("-") + + return [ + DefaultInfo(files = depset([gem])), + GemInfo( + name = name, + version = version, + ), + ] + +rb_gem = rule( + _rb_gem_impl, + attrs = { + "gem": attr.label( + allow_single_file = [".gem"], + mandatory = True, + doc = "Gem file.", + ), + }, + doc = """ +Exposes a Ruby gem file. + +You normally don't need to call this rule directly as it's an internal one +used by `rb_bundle_fetch()`. + """, +) diff --git a/ruby/private/gem_build.bzl b/ruby/private/gem_build.bzl index 4af88111..f5d8cd05 100644 --- a/ruby/private/gem_build.bzl +++ b/ruby/private/gem_build.bzl @@ -3,31 +3,33 @@ load("//ruby/private:library.bzl", LIBRARY_ATTRS = "ATTRS") load( "//ruby/private:providers.bzl", + "BundlerInfo", "RubyFilesInfo", "get_bundle_env", "get_transitive_data", "get_transitive_deps", "get_transitive_srcs", ) +load("//ruby/private:utils.bzl", _is_windows = "is_windows") def _rb_gem_build_impl(ctx): - env = {} - windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo] - is_windows = ctx.target_platform_has_constraint(windows_constraint) tools = depset([]) gem_builder = ctx.actions.declare_file("{}_gem_builder.rb".format(ctx.label.name)) transitive_data = get_transitive_data(ctx.files.data, ctx.attr.deps).to_list() - transitive_deps = get_transitive_deps(ctx.attr.deps) + transitive_deps = get_transitive_deps(ctx.attr.deps).to_list() transitive_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps).to_list() bundle_env = get_bundle_env({}, ctx.attr.deps) java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] ruby_toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] + env = {} + env.update(ruby_toolchain.env) + if ruby_toolchain.version.startswith("jruby"): env["JAVA_HOME"] = java_toolchain.java_runtime.java_home tools = java_toolchain.java_runtime.files - if is_windows: + if _is_windows(ctx): env["PATH"] = ruby_toolchain.ruby.dirname # Inputs manifest is a dictionary where: @@ -58,23 +60,38 @@ def _rb_gem_build_impl(ctx): args = ctx.actions.args() args.add(gem_builder) ctx.actions.run( - inputs = depset(inputs), executable = ruby_toolchain.ruby, - arguments = [args], + inputs = depset(inputs), outputs = [ctx.outputs.gem], + arguments = [args], env = env, - use_default_shell_env = not is_windows, + mnemonic = "GemBuild", tools = tools, + use_default_shell_env = not _is_windows(ctx), ) - return [ + providers = [] + runfiles = ctx.runfiles(transitive_srcs + transitive_data) + for dep in transitive_deps: + if BundlerInfo in dep: + providers.append(dep[BundlerInfo]) + runfiles.merge(ctx.runfiles([dep[BundlerInfo].gemfile, dep[BundlerInfo].path])) + break + + providers.extend([ + DefaultInfo( + files = depset([ctx.outputs.gem]), + runfiles = runfiles, + ), RubyFilesInfo( transitive_data = depset(transitive_data), - transitive_deps = transitive_deps, + transitive_deps = depset(transitive_deps), transitive_srcs = depset(transitive_srcs), bundle_env = bundle_env, ), - ] + ]) + + return providers rb_gem_build = rule( _rb_gem_build_impl, @@ -87,7 +104,7 @@ rb_gem_build = rule( ), _gem_builder_tpl = attr.label( allow_single_file = True, - default = "@rules_ruby//ruby/private:gem_build/gem_builder.rb.tpl", + default = "@rules_ruby//ruby/private/gem_build:gem_builder.rb.tpl", ), _windows_constraint = attr.label( default = "@platforms//os:windows", diff --git a/ruby/private/gem_build/BUILD b/ruby/private/gem_build/BUILD new file mode 100644 index 00000000..350ec67a --- /dev/null +++ b/ruby/private/gem_build/BUILD @@ -0,0 +1 @@ +exports_files(["gem_builder.rb.tpl"]) diff --git a/ruby/private/gem_build/gem_builder.rb.tpl b/ruby/private/gem_build/gem_builder.rb.tpl index 3d2a7a49..883ecd11 100644 --- a/ruby/private/gem_build/gem_builder.rb.tpl +++ b/ruby/private/gem_build/gem_builder.rb.tpl @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'json' require 'rubygems/package' @@ -34,7 +36,7 @@ Dir.mktmpdir do |tmpdir| gemspec_code = File.read(gemspec_path) Dir.chdir(gemspec_dir) do - spec = binding.eval(gemspec_code, gemspec_file, __LINE__) + spec = binding.eval(gemspec_code, gemspec_file, __LINE__) # rubocop:disable Security/Eval file = Gem::Package.build(spec) FileUtils.mv(file, packaged_gem_path) end diff --git a/ruby/private/gem_install.bzl b/ruby/private/gem_install.bzl new file mode 100644 index 00000000..3a7afa94 --- /dev/null +++ b/ruby/private/gem_install.bzl @@ -0,0 +1,137 @@ +"Implementation details for rb_gem_install" + +load("//ruby/private:providers.bzl", "GemInfo") +load( + "//ruby/private:utils.bzl", + _convert_env_to_script = "convert_env_to_script", + _is_windows = "is_windows", + _normalize_path = "normalize_path", +) + +def _rb_gem_install_impl(ctx): + gem = ctx.file.gem + install_dir = ctx.actions.declare_directory(gem.basename[:-4]) + toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] + + env = {} + env.update(toolchain.env) + tools = [toolchain.gem] + if toolchain.version.startswith("jruby"): + java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] + tools.extend(java_toolchain.java_runtime.files.to_list()) + env.update({"JAVA_HOME": java_toolchain.java_runtime.java_home}) + + if _is_windows(ctx): + gem_install = ctx.actions.declare_file("gem_install_{}.cmd".format(ctx.label.name)) + template = ctx.file._gem_install_cmd_tpl + env.update({"PATH": _normalize_path(ctx, toolchain.bindir) + ";%PATH%"}) + else: + gem_install = ctx.actions.declare_file("gem_install_{}.sh".format(ctx.label.name)) + template = ctx.file._gem_install_sh_tpl + env.update({"PATH": "%s:$PATH" % toolchain.bindir}) + + ctx.actions.expand_template( + template = template, + output = gem_install, + substitutions = { + "{env}": _convert_env_to_script(ctx, env), + "{gem_binary}": _normalize_path(ctx, toolchain.gem.path), + "{gem}": gem.path, + "{install_dir}": install_dir.path, + }, + ) + + name, _, version = ctx.attr.name.rpartition("-") + ctx.actions.run( + executable = gem_install, + inputs = depset([gem, gem_install]), + outputs = [install_dir], + mnemonic = "GemInstall", + progress_message = "Installing %{input} (%{label})", + tools = tools, + use_default_shell_env = True, + ) + + return [ + DefaultInfo(files = depset([gem, install_dir])), + GemInfo( + name = name, + version = version, + ), + ] + +rb_gem_install = rule( + _rb_gem_install_impl, + attrs = { + "gem": attr.label( + allow_single_file = [".gem"], + mandatory = True, + doc = "Gem file to install.", + ), + "_gem_install_cmd_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//ruby/private/gem_install:gem_install.cmd.tpl", + ), + "_gem_install_sh_tpl": attr.label( + allow_single_file = True, + default = "@rules_ruby//ruby/private/gem_install:gem_install.sh.tpl", + ), + "_windows_constraint": attr.label( + default = "@platforms//os:windows", + ), + }, + toolchains = [ + "@rules_ruby//ruby:toolchain_type", + "@bazel_tools//tools/jdk:runtime_toolchain_type", + ], + doc = """ +Installs a built Ruby gem. + +Suppose you have the following Ruby gem, where `rb_library()` is used +in `BUILD` files to define the packages for the gem and `rb_gem_build()` is used +to build a Ruby gem package from the sources. + +```output +|-- BUILD +|-- Gemfile +|-- WORKSPACE +|-- gem.gemspec +`-- lib + |-- BUILD + |-- gem + | |-- BUILD + | |-- add.rb + | |-- subtract.rb + | `-- version.rb + `-- gem.rb +``` + +You can now install the built `.gem` file by defining a target: + +`BUILD`: +```bazel +load("@rules_ruby//ruby:defs.bzl", "rb_gem_build", "rb_gem_install") + +package(default_visibility = ["//:__subpackages__"]) + +rb_gem_build( + name = "gem-build", + gemspec = "gem.gemspec", + deps = ["//lib:gem"], +) + +rb_gem_install( + name = "gem-install", + gem = ":gem-build", +) +``` + +```output +$ bazel build :gem-install +INFO: Analyzed target //:gem-install (4 packages loaded, 82 targets configured). +INFO: From Installing bazel-out/darwin_arm64-fastbuild/bin/gem-build.gem (//:gem-install): +Successfully installed example-0.1.0 +1 gem installed +``` + """, +) diff --git a/ruby/private/gem_install/BUILD b/ruby/private/gem_install/BUILD new file mode 100644 index 00000000..f7f1ec9e --- /dev/null +++ b/ruby/private/gem_install/BUILD @@ -0,0 +1,4 @@ +exports_files([ + "gem_install.cmd.tpl", + "gem_install.sh.tpl", +]) diff --git a/ruby/private/gem_install/gem_install.cmd.tpl b/ruby/private/gem_install/gem_install.cmd.tpl new file mode 100644 index 00000000..50c392aa --- /dev/null +++ b/ruby/private/gem_install/gem_install.cmd.tpl @@ -0,0 +1,16 @@ +@echo off + +{env} + +{gem_binary} ^ + install ^ + {gem} ^ + --wrappers ^ + --ignore-dependencies ^ + --local ^ + --no-document ^ + --no-env-shebang ^ + --install-dir {install_dir} ^ + --bindir {install_dir}/bin + +:: vim: ft=dosbatch diff --git a/ruby/private/gem_install/gem_install.sh.tpl b/ruby/private/gem_install/gem_install.sh.tpl new file mode 100644 index 00000000..89e8f059 --- /dev/null +++ b/ruby/private/gem_install/gem_install.sh.tpl @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +{env} + +{gem_binary} \ + install \ + {gem} \ + --wrappers \ + --ignore-dependencies \ + --local \ + --no-document \ + --no-env-shebang \ + --install-dir {install_dir} \ + --bindir {install_dir}/bin + +# vim: ft=bash diff --git a/ruby/private/gem_push.bzl b/ruby/private/gem_push.bzl index daf2cc58..3939e7aa 100644 --- a/ruby/private/gem_push.bzl +++ b/ruby/private/gem_push.bzl @@ -1,4 +1,4 @@ -"Implementation details for gem_push" +"Implementation details for rb_gem_push" load("//ruby/private:binary.bzl", "generate_rb_binary_script", BINARY_ATTRS = "ATTRS") load("//ruby/private:library.bzl", LIBRARY_ATTRS = "ATTRS") @@ -8,7 +8,7 @@ def _rb_gem_push_impl(ctx): java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"] ruby_toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"] srcs = [ctx.file.gem] - tools = [ruby_toolchain.gem] + tools = [ruby_toolchain.gem, ctx.file._runfiles_library] if ruby_toolchain.version.startswith("jruby"): env["JAVA_HOME"] = java_toolchain.java_runtime.java_home @@ -52,6 +52,7 @@ Gem file to push to RubyGems. You would usually use an output of `rb_gem_build() _binary_cmd_tpl = BINARY_ATTRS["_binary_cmd_tpl"], _binary_sh_tpl = BINARY_ATTRS["_binary_sh_tpl"], _windows_constraint = BINARY_ATTRS["_windows_constraint"], + _runfiles_library = BINARY_ATTRS["_runfiles_library"], ), toolchains = [ "@rules_ruby//ruby:toolchain_type", diff --git a/ruby/private/library.bzl b/ruby/private/library.bzl index 82f8a6a6..e245f174 100644 --- a/ruby/private/library.bzl +++ b/ruby/private/library.bzl @@ -2,6 +2,7 @@ load( "//ruby/private:providers.bzl", + "BundlerInfo", "RubyFilesInfo", "get_bundle_env", "get_transitive_data", @@ -37,7 +38,7 @@ def _rb_library_impl(ctx): runfiles = ctx.runfiles(transitive_srcs + transitive_data) runfiles = get_transitive_runfiles(runfiles, ctx.attr.srcs, ctx.attr.deps, ctx.attr.data) - return [ + providers = [ DefaultInfo( files = depset(transitive_srcs + transitive_data), runfiles = runfiles, @@ -50,6 +51,13 @@ def _rb_library_impl(ctx): ), ] + for dep in transitive_deps: + if BundlerInfo in dep: + providers.append(dep[BundlerInfo]) + break + + return providers + rb_library = rule( implementation = _rb_library_impl, attrs = ATTRS, diff --git a/ruby/private/providers.bzl b/ruby/private/providers.bzl index e4e75df0..9eca0ab9 100644 --- a/ruby/private/providers.bzl +++ b/ruby/private/providers.bzl @@ -1,9 +1,20 @@ -"Providers for Interoperability between rules" +"Providers for interoperability between rules" + RubyFilesInfo = provider( "Provider for Ruby files", fields = ["transitive_data", "transitive_deps", "transitive_srcs", "bundle_env"], ) +BundlerInfo = provider( + "Provider for Bundler installation", + fields = ["bin", "gemfile", "path", "env"], +) + +GemInfo = provider( + "Provider for a packed Ruby gem", + fields = ["name", "version"], +) + # https://bazel.build/rules/depsets def get_transitive_srcs(srcs, deps): @@ -79,9 +90,10 @@ def get_bundle_env(envs, deps): transitive_deps = get_transitive_deps(deps).to_list() for dep in transitive_deps: bundle_env.update(dep[RubyFilesInfo].bundle_env) + if BundlerInfo in dep: + bundle_env.update(dep[BundlerInfo].env) for env in envs: if env.startswith("BUNDLE_"): bundle_env[env] = envs[env] - return bundle_env diff --git a/ruby/private/test.bzl b/ruby/private/test.bzl index 48ee67a4..551be267 100644 --- a/ruby/private/test.bzl +++ b/ruby/private/test.bzl @@ -58,7 +58,7 @@ rb_test( name = "add", srcs = ["add_spec.rb"], args = ["spec/add_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [ ":spec_helper", "@bundle", @@ -69,7 +69,7 @@ rb_test( name = "subtract", srcs = ["subtract_spec.rb"], args = ["spec/subtract_spec.rb"], - main = "@bundle//:bin/rspec", + main = "@bundle//bin:rspec", deps = [ ":spec_helper", "@bundle", @@ -103,7 +103,7 @@ package(default_visibility = ["//:__subpackages__"]) rb_test( name = "rubocop", args = ["lib/"], - main = "@bundle//:bin/rubocop", + main = "@bundle//bin:rubocop", tags = ["no-sandbox"], deps = [ "//lib:gem", diff --git a/ruby/private/toolchain.bzl b/ruby/private/toolchain.bzl index 832da8d0..0040a0fc 100644 --- a/ruby/private/toolchain.bzl +++ b/ruby/private/toolchain.bzl @@ -5,7 +5,13 @@ load("//ruby/private/toolchain:repository_proxy.bzl", _rb_toolchain_repository_p DEFAULT_RUBY_REPOSITORY = "ruby" -def rb_register_toolchains(name = DEFAULT_RUBY_REPOSITORY, version = None, version_file = None, register = True, **kwargs): +def rb_register_toolchains( + name = DEFAULT_RUBY_REPOSITORY, + version = None, + version_file = None, + msys2_packages = ["libyaml"], + register = True, + **kwargs): """ Register a Ruby toolchain and lazily download the Ruby Interpreter. @@ -13,7 +19,7 @@ def rb_register_toolchains(name = DEFAULT_RUBY_REPOSITORY, version = None, versi * _(For MRI on Windows)_ Installed using [RubyInstaller](https://rubyinstaller.org). * _(For JRuby on any OS)_ Downloaded and installed directly from [official website](https://www.jruby.org). * _(For TruffleRuby on Linux and macOS)_ Installed using [ruby-build](https://github.com/rbenv/ruby-build). - * _(For "system") Ruby found on the PATH is used. Please note that builds are not hermetic in this case. + * _(For "system")_ Ruby found on the PATH is used. Please note that builds are not hermetic in this case. `WORKSPACE`: ```bazel @@ -28,6 +34,7 @@ def rb_register_toolchains(name = DEFAULT_RUBY_REPOSITORY, version = None, versi name: base name of resulting repositories, by default "rules_ruby" version: a semver version of MRI, or a string like [interpreter type]-[version], or "system" version_file: .ruby-version or .tool-versions file to read version from + msys2_packages: extra MSYS2 packages to install register: whether to register the resulting toolchains, should be False under bzlmod **kwargs: additional parameters to the downloader for this interpreter type """ @@ -37,6 +44,7 @@ def rb_register_toolchains(name = DEFAULT_RUBY_REPOSITORY, version = None, versi name = name, version = version, version_file = version_file, + msys2_packages = msys2_packages, **kwargs ) _rb_toolchain_repository_proxy( diff --git a/ruby/private/binary/rlocation.bzl b/ruby/private/utils.bzl similarity index 50% rename from ruby/private/binary/rlocation.bzl rename to ruby/private/utils.bzl index 1e06c37d..39f8fed5 100644 --- a/ruby/private/binary/rlocation.bzl +++ b/ruby/private/utils.bzl @@ -59,3 +59,83 @@ exit /b 0 :rlocation_end :: End of rlocation """ + +def is_windows(ctx): + windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo] + return ctx.target_platform_has_constraint(windows_constraint) + +def convert_env_to_script(ctx, env): + """Converts an env dictionary to a string of batch/shell commands. + + Args: + ctx: rule context + env: dictionary of environment variables + + Returns: + a string with export environment variables commands. + """ + environment = [] + if is_windows(ctx): + export_command = "set" + else: + export_command = "export" + + for (name, value) in env.items(): + command = "{command} {name}={value}".format(command = export_command, name = name, value = value) + environment.append(command) + + return "\n".join(environment) + +def normalize_path(ctx, path): + """Converts path to an OS-specific equivalent. + + Args: + ctx: rule context + path: filepath string + + Returns: + an OS-specific path. + """ + if is_windows(ctx): + return path.replace("/", "\\") + else: + return path.replace("\\", "/") + +def join_and_indent(names, indentation_level = 2): + """Convers a list of strings to a pretty indented BUILD variant. + + Args: + names: list of strings + indentation_level: how many 4 spaces to indent with + + Returns: + indented string + """ + indentation = "" + for _ in range(0, indentation_level): + indentation += " " + + string = "[" + for name in names: + string += '\n%s"%s",' % (indentation, name) + string += "\n%s]" % indentation[:-4] + + return string + +def normalize_bzlmod_repository_name(name): + """Converts Bzlmod repostory to its private name. + + This is needed to define a target that is called the same as the repository. + For example, given a canonical name "rules_ruby~override~ruby~bundle", + the function would return "bundle" as the name. + + This is a hacky workaround and will be fixed upstream. + See https://github.com/bazelbuild/bazel/issues/20486. + + Args: + name: canonical repository name + + Returns: + repository name + """ + return name.rpartition("~")[-1] diff --git a/ruby/toolchain.bzl b/ruby/toolchain.bzl index 20743f53..96d766af 100644 --- a/ruby/toolchain.bzl +++ b/ruby/toolchain.bzl @@ -7,6 +7,7 @@ def _rb_toolchain_impl(ctx): gem = ctx.executable.gem, bindir = ctx.attr.bindir, version = ctx.attr.version, + env = ctx.attr.env, ) rb_toolchain = rule( @@ -36,5 +37,8 @@ rb_toolchain = rule( "version": attr.string( doc = "Ruby version", ), + "env": attr.string_dict( + doc = "Environment variables required by an interpreter", + ), }, ) From 47a3a51e792623fb5549f4fb16ebe46c650ecb7c Mon Sep 17 00:00:00 2001 From: Alex Rodionov Date: Tue, 9 Jan 2024 13:15:39 -0800 Subject: [PATCH 2/2] chore: keep `MODULE.bazel.lock` in repo --- .github/workflows/ci.yml | 1 + .../MODULE.bazel.lock => MODULE.bazel.lock | 228 +++++------------- examples/gem/.bazelrc | 3 + 3 files changed, 68 insertions(+), 164 deletions(-) rename examples/gem/MODULE.bazel.lock => MODULE.bazel.lock (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46c66757..ec9d271e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,7 @@ jobs: - WORKSPACE exclude: # JRuby with bzlmod fails with long path issues on Windows. + # See #64 - os: windows ruby: jruby-9.4.5.0 mode: bzlmod diff --git a/examples/gem/MODULE.bazel.lock b/MODULE.bazel.lock similarity index 96% rename from examples/gem/MODULE.bazel.lock rename to MODULE.bazel.lock index 46af70c5..120e7cc0 100644 --- a/examples/gem/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,6 +1,6 @@ { "lockFileVersion": 3, - "moduleFileHash": "927e5313256a133827ed4814eb4dacff4884c3f2b77a6047917ab5e542e95d23", + "moduleFileHash": "505c065918d99a227d7f5250dd0e3d27bbc3d6409f8623f35e32583718bd7a7d", "flags": { "cmdRegistries": [ "https://bcr.bazel.build/" @@ -13,87 +13,28 @@ "compatibilityMode": "ERROR" }, "localOverrideHashes": { - "bazel_tools": "922ea6752dc9105de5af957f7a99a6933c0a6a712d23df6aad16a9c399f7e787", - "rules_ruby": "505c065918d99a227d7f5250dd0e3d27bbc3d6409f8623f35e32583718bd7a7d" + "bazel_tools": "922ea6752dc9105de5af957f7a99a6933c0a6a712d23df6aad16a9c399f7e787" }, "moduleDepGraph": { "": { - "name": "", - "version": "", + "name": "rules_ruby", + "version": "0.0.0", "key": "", - "repoName": "", + "repoName": "rules_ruby", "executionPlatformsToRegister": [], - "toolchainsToRegister": [ - "@ruby_toolchains//:all" - ], - "extensionUsages": [ - { - "extensionBzlFile": "@rules_ruby//ruby:extensions.bzl", - "extensionName": "ruby", - "usingModule": "", - "location": { - "file": "@@//:MODULE.bazel", - "line": 10, - "column": 21 - }, - "imports": { - "ruby": "ruby", - "bundle": "bundle", - "ruby_toolchains": "ruby_toolchains" - }, - "devImports": [], - "tags": [ - { - "tagName": "toolchain", - "attributeValues": { - "name": "ruby", - "version_file": "//:.ruby-version" - }, - "devDependency": false, - "location": { - "file": "@@//:MODULE.bazel", - "line": 11, - "column": 15 - } - }, - { - "tagName": "bundle", - "attributeValues": { - "name": "bundle", - "srcs": [ - "//:Gemfile.lock", - "//:gem.gemspec", - "//:lib/gem/version.rb" - ], - "env": { - "BUNDLE_BUILD__FOO": "bar" - }, - "gemfile": "//:Gemfile", - "toolchain": "@ruby//:BUILD" - }, - "devDependency": false, - "location": { - "file": "@@//:MODULE.bazel", - "line": 16, - "column": 12 - } - } - ], - "hasDevUseExtension": false, - "hasNonDevUseExtension": true - } - ], + "toolchainsToRegister": [], + "extensionUsages": [], "deps": { - "bazel_skylib": "bazel_skylib@1.5.0", - "rules_ruby": "rules_ruby@_", + "bazel_skylib": "bazel_skylib@1.3.0", + "platforms": "platforms@0.0.7", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" } }, - "bazel_skylib@1.5.0": { + "bazel_skylib@1.3.0": { "name": "bazel_skylib", - "version": "1.5.0", - "key": "bazel_skylib@1.5.0", + "version": "1.3.0", + "key": "bazel_skylib@1.3.0", "repoName": "bazel_skylib", "executionPlatformsToRegister": [], "toolchainsToRegister": [ @@ -110,30 +51,43 @@ "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", "ruleClassName": "http_archive", "attributes": { - "name": "bazel_skylib~1.5.0", + "name": "bazel_skylib~1.3.0", "urls": [ - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz" ], - "integrity": "sha256-zVWgYudjuTSZIfD124w5MyiNyLpPdt2UFqrGis7jy5Q=", + "integrity": "sha256-dNVE2W9KW7Yw1GXKi7z+Ix41lOWq5X4e2/F6brPKJQY=", "strip_prefix": "", "remote_patches": {}, "remote_patch_strip": 0 } } }, - "rules_ruby@_": { - "name": "rules_ruby", - "version": "0.0.0", - "key": "rules_ruby@_", - "repoName": "rules_ruby", + "platforms@0.0.7": { + "name": "platforms", + "version": "0.0.7", + "key": "platforms@0.0.7", + "repoName": "platforms", "executionPlatformsToRegister": [], "toolchainsToRegister": [], "extensionUsages": [], "deps": { - "bazel_skylib": "bazel_skylib@1.5.0", - "platforms": "platforms@0.0.7", + "rules_license": "rules_license@0.0.7", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "platforms", + "urls": [ + "https://github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz" + ], + "integrity": "sha256-OlYcmee9vpFzqmU/1Xn+hJ8djWc5V4CrR3Cx84FDHVE=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } } }, "bazel_tools@_": { @@ -283,16 +237,15 @@ "bazel_tools": "bazel_tools@_" } }, - "platforms@0.0.7": { - "name": "platforms", + "rules_license@0.0.7": { + "name": "rules_license", "version": "0.0.7", - "key": "platforms@0.0.7", - "repoName": "platforms", + "key": "rules_license@0.0.7", + "repoName": "rules_license", "executionPlatformsToRegister": [], "toolchainsToRegister": [], "extensionUsages": [], "deps": { - "rules_license": "rules_license@0.0.7", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" }, @@ -300,11 +253,11 @@ "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", "ruleClassName": "http_archive", "attributes": { - "name": "platforms", + "name": "rules_license~0.0.7", "urls": [ - "https://github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz" + "https://github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz" ], - "integrity": "sha256-OlYcmee9vpFzqmU/1Xn+hJ8djWc5V4CrR3Cx84FDHVE=", + "integrity": "sha256-RTHezLkTY5ww5cdRKgVNXYdWmNrrddjPkPKEN1/nw2A=", "strip_prefix": "", "remote_patches": {}, "remote_patch_strip": 0 @@ -441,7 +394,7 @@ "deps": { "platforms": "platforms@0.0.7", "rules_cc": "rules_cc@0.0.9", - "bazel_skylib": "bazel_skylib@1.5.0", + "bazel_skylib": "bazel_skylib@1.3.0", "rules_proto": "rules_proto@4.0.0", "rules_license": "rules_license@0.0.7", "bazel_tools": "bazel_tools@_", @@ -462,33 +415,6 @@ } } }, - "rules_license@0.0.7": { - "name": "rules_license", - "version": "0.0.7", - "key": "rules_license@0.0.7", - "repoName": "rules_license", - "executionPlatformsToRegister": [], - "toolchainsToRegister": [], - "extensionUsages": [], - "deps": { - "bazel_tools": "bazel_tools@_", - "local_config_platform": "local_config_platform@_" - }, - "repoSpec": { - "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", - "ruleClassName": "http_archive", - "attributes": { - "name": "rules_license~0.0.7", - "urls": [ - "https://github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz" - ], - "integrity": "sha256-RTHezLkTY5ww5cdRKgVNXYdWmNrrddjPkPKEN1/nw2A=", - "strip_prefix": "", - "remote_patches": {}, - "remote_patch_strip": 0 - } - } - }, "rules_proto@4.0.0": { "name": "rules_proto", "version": "4.0.0", @@ -498,7 +424,7 @@ "toolchainsToRegister": [], "extensionUsages": [], "deps": { - "bazel_skylib": "bazel_skylib@1.5.0", + "bazel_skylib": "bazel_skylib@1.3.0", "rules_cc": "rules_cc@0.0.9", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" @@ -584,7 +510,7 @@ "toolchainsToRegister": [], "extensionUsages": [], "deps": { - "bazel_skylib": "bazel_skylib@1.5.0", + "bazel_skylib": "bazel_skylib@1.3.0", "zlib": "zlib@1.3", "rules_python": "rules_python@0.4.0", "rules_cc": "rules_cc@0.0.9", @@ -675,7 +601,7 @@ } ], "deps": { - "bazel_skylib": "bazel_skylib@1.5.0", + "bazel_skylib": "bazel_skylib@1.3.0", "platforms": "platforms@0.0.7", "bazel_tools": "bazel_tools@_", "local_config_platform": "local_config_platform@_" @@ -743,6 +669,24 @@ } } }, + "@@bazel_tools//tools/osx:xcode_configure.bzl%xcode_configure_extension": { + "general": { + "bzlTransitiveDigest": "Qh2bWTU6QW6wkrd87qrU4YeY+SG37Nvw3A0PR4Y0L2Y=", + "accumulatedFileDigests": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_xcode": { + "bzlFile": "@@bazel_tools//tools/osx:xcode_configure.bzl", + "ruleClassName": "xcode_autoconf", + "attributes": { + "name": "bazel_tools~xcode_configure_extension~local_config_xcode", + "xcode_locator": "@bazel_tools//tools/osx:xcode_locator.m", + "remote_xcode": "" + } + } + } + } + }, "@@bazel_tools//tools/sh:sh_configure.bzl%sh_configure_extension": { "general": { "bzlTransitiveDigest": "hp4NgmNjEg5+xgvzfh6L83bt9/aiiWETuNpwNuF1MSU=", @@ -1298,50 +1242,6 @@ } } } - }, - "@@rules_ruby~override//ruby:extensions.bzl%ruby": { - "general": { - "bzlTransitiveDigest": "SQxPoUdQVlY1E52xJKuldVUzDBzhDAK9Le3Dihl5TZo=", - "accumulatedFileDigests": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "ruby_toolchains": { - "bzlFile": "@@rules_ruby~override//ruby/private/toolchain:repository_proxy.bzl", - "ruleClassName": "rb_toolchain_repository_proxy", - "attributes": { - "name": "rules_ruby~override~ruby~ruby_toolchains", - "toolchain": "@ruby//:toolchain", - "toolchain_type": "@rules_ruby//ruby:toolchain_type" - } - }, - "bundle": { - "bzlFile": "@@rules_ruby~override//ruby/private:bundle.bzl", - "ruleClassName": "rb_bundle", - "attributes": { - "toolchain": "@@rules_ruby~override~ruby~ruby//:BUILD", - "name": "rules_ruby~override~ruby~bundle", - "srcs": [ - "@@//:Gemfile.lock", - "@@//:gem.gemspec", - "@@//:lib/gem/version.rb" - ], - "env": { - "BUNDLE_BUILD__FOO": "bar" - }, - "gemfile": "@@//:Gemfile" - } - }, - "ruby": { - "bzlFile": "@@rules_ruby~override//ruby/private:download.bzl", - "ruleClassName": "rb_download", - "attributes": { - "name": "rules_ruby~override~ruby~ruby", - "version": "", - "version_file": "@@//:.ruby-version" - } - } - } - } } } } diff --git a/examples/gem/.bazelrc b/examples/gem/.bazelrc index 7cdeaddf..0b8abb3f 100644 --- a/examples/gem/.bazelrc +++ b/examples/gem/.bazelrc @@ -198,6 +198,9 @@ test --test_verbose_timeout_warnings=false build --action_env=HOME test --test_env=HOME +# Not ready for the MODULE.lock file yet, as of Bazel 7.0.0 there are still some stability issues. +common --lockfile_mode=off + # Allows to run tests with rdbg: # 1. Add breakpoint with `binding.break`. # 2. Run tests: `bazel test --config debug spec:add`.