diff --git a/.bazelversion b/.bazelversion index 227cea2..3e3c2f1 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -2.0.0 +2.1.1 diff --git a/.circleci/.bazelrc b/.circleci/.bazelrc index 4a379b6..3d8914d 100644 --- a/.circleci/.bazelrc +++ b/.circleci/.bazelrc @@ -1,6 +1,17 @@ -build --verbose_failures --spawn_strategy=standalone --strategy=Genrule=standalone --show_timestamps --show_progress --show_progress_rate_limit 0.5 -test --spawn_strategy=standalone +# Common options common --color=yes -startup --max_idle_secs=1 -test --verbose_failures --test_output=errors --test_verbose_timeout_warnings --show_progress --show_progress_rate_limit 0.5 -run --verbose_failures --show_progress --show_progress_rate_limit 0.5 +common --show_progress +common --show_progress_rate_limit=0.5 + +# Build options +build --spawn_strategy=standalone +build --strategy=Genrule=standalone +build --show_timestamps +build --curses=no +build --jobs=10 + +# Test options +test --spawn_strategy=standalone +test --test_output=all +test --test_verbose_timeout_warnings +test --verbose_failures diff --git a/.circleci/Dockerfile b/.circleci/Dockerfile index 1419792..155b09c 100644 --- a/.circleci/Dockerfile +++ b/.circleci/Dockerfile @@ -1,7 +1,7 @@ # # rules_ruby circleci Docker file. # -FROM ruby:2.6.5-stretch +FROM ruby:2.7.0 # make Apt non-interactive RUN echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/90circleci \ @@ -90,9 +90,9 @@ RUN groupadd --gid 3434 circleci \ RUN apt-get update && apt-get upgrade -RUN apt-get install -y openjdk-8-jdk python2.7 python3 golang-go +RUN apt-get install -y openjdk-11-jdk python2.7 python3 golang-go -RUN curl -L -o /usr/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v1.0/bazelisk-linux-amd64 \ +RUN curl -L -o /usr/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v1.3.0/bazelisk-linux-amd64 \ && sudo chmod +x /usr/bin/bazel USER circleci diff --git a/.circleci/build.sh b/.circleci/build.sh index 819d7b5..b775eea 100755 --- a/.circleci/build.sh +++ b/.circleci/build.sh @@ -1,7 +1,18 @@ #!/usr/bin/env bash -set -ex +set -e -docker build . -t bazelruby/ruby-2.6.5 -docker push bazelruby/ruby-2.6.5 +[[ -d .circleci ]] && cd .circleci + +RUBY_VERSION=$(cat ../.ruby-version) + +echo +echo "Ruby version is $RUBY_VERSION" +echo + +set -x + +docker build . -t bazelruby/ruby-$RUBY_VERSION + +docker push bazelruby/ruby-$RUBY_VERSION diff --git a/.circleci/config.yml b/.circleci/config.yml index 6bbf76d..b9e9884 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,11 +5,11 @@ jobs: working_directory: /home/circleci/repo resource_class: medium docker: - - image: bazelruby/ruby-2.6.5 + - image: bazelruby/ruby-2.7.0 environment: PATH: "/usr/local/bin:/usr/bin:/sbin:/opt/bin:/home/circleci/repo/bin:/bin:/sbin:/usr/sbin" BUNDLE_PATH: /home/circleci/.bundle_cache - BAZEL_OPTS: "--max_idle_secs=1 --host_jvm_args=-Xmx500m --host_jvm_args=-Xms500m" + BAZEL_OPTS: "--host_jvm_args=-Xmx500m --host_jvm_args=-Xms500m" BAZEL_BUILD_OPTS: "--curses=no --verbose_failures --jobs 10" BAZEL_TEST_OPTS: "--verbose_failures --test_output=streamed --test_verbose_timeout_warnings " @@ -48,7 +48,7 @@ jobs: command: | /usr/bin/env bash bin/test-suite workspace - bazel_build_examples: + bazel_example_script: <<: *bazel_defaults steps: @@ -61,24 +61,26 @@ jobs: /usr/bin/env bash bin/setup - run: - name: "Show Bazel Info" + name: "Bazel Build & Test Example" command: | - /usr/bin/env bash bin/test-suite bazel-info + /usr/bin/env bash bin/test-suite simple-script - - run: - name: "Bazel Simple Script Example Build" - command: | - cd examples/simple_script && bazel ${BAZEL_OPTS} build ${BAZEL_BUILD_OPTS} -- //... + bazel_example_gem: + <<: *bazel_defaults + + steps: + - checkout - run: - name: "Bazel Simple Script Example Test" + name: "Install ~/.bazelrc and run setup" command: | - cd examples/simple_script && bazel ${BAZEL_OPTS} test ${BAZEL_BUILD_OPTS} ${BAZEL_TEST_OPTS} -- //... + cp .circleci/.bazelrc ${HOME} + /usr/bin/env bash bin/setup - run: - name: "Bazel Simple Script Example Rubocop Check" + name: "Bazel Build & Test Example" command: | - cd examples/simple_script && bazel ${BAZEL_OPTS} run ${BAZEL_BUILD_OPTS} -- :rubocop + /usr/bin/env bash bin/test-suite example-gem buildifier: <<: *bazel_defaults @@ -106,5 +108,6 @@ workflows: rules_ruby: jobs: - bazel_build_workspace - - bazel_build_examples + - bazel_example_gem + - bazel_example_script - buildifier diff --git a/.rubocop.yml b/.rubocop.yml index f4a96ee..0d331a5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,12 +1,36 @@ inherit_from: .relaxed-rubocop-2.4.yml AllCops: - TargetRubyVersion: 2.6.5 + TargetRubyVersion: 2.6 + UseCache: true + DefaultFormatter: progress + DisplayStyleGuide: true + DisplayCopNames: true Exclude: - - "**/rubocop" - - "examples/**/*" + - "external*/**/*" - "bazel-*/**/*" - - "**/external/**/*" + - "**/examples/**/*" + - "**/BUILD" + - "**/*.bazel" + - "**/*.bzl" + - "**/rubocop" - "**/vendor/bundle/**/*" + Include: + - '**/*.rb' + - '**/*.gemfile' + - '**/*.gemspec' + - '**/*.rake' + - '**/*.ru' + - '**/Gemfile' + - '**/Rakefile' +Layout/HashAlignment: + Enabled: true + EnforcedColonStyle: table + +Style/Dir: + Enabled: false +Layout/MultilineMethodCallIndentation: + Enabled: true + EnforcedStyle: indented_relative_to_receiver diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 57cf282..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.6.5 diff --git a/.travis.yml b/.travis.yml index d212be2..3363cb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,6 @@ env: - CI=true - BUNDLE_PATH="${HOME}/.bundle/gems" - PATH="${HOME}/.rbenv/bin:${HOME}/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin:/opt/local/bin:${PATH}" - - BAZEL_OPTS="--max_idle_secs=1 --host_jvm_args=-Xmx1200m --host_jvm_args=-Xms1200m" - - BAZEL_BUILD_OPTS="--curses=no --verbose_failures -j 30 --show_progress_rate_limit 0.5" + - BAZEL_OPTS="--host_jvm_args=-Xmx1200m --host_jvm_args=-Xms1200m" + - BAZEL_BUILD_OPTS="--curses=no --verbose_failures -j 10 --show_progress_rate_limit 0.5" - BAZEL_TEST_OPTS="--verbose_failures --test_output=streamed --test_verbose_timeout_warnings" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e627bc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# 0.3.0 / 2020-03-02 + +[Total Changes since v0.2.0](https://github.com/bazelruby/rules_ruby/compare/v0.2.0...v0.3.0) + +**2,221 additions and 631 deletions in 71 changed files.** + +## Backwards Incompatible Changes + +* Main workspace has been renamed from `bazelruby_ruby_rules` to `bazelruby_rules_ruby` +* Global function `ruby_register_toolchains` has been renamed to `rules_ruby_select_sdk` +* Global function `ruby_rules_dependencies` has been renamed to `rules_ruby_dependencies` +* `bundle_install` has been removed in favor of `ruby_bundle`. + +## Other Changes + +* Introduced `ruby_gem` rule for packaging Ruby sources into a RubyGem-compatible zip file. Note, the resulting file has `.zip` extension. +* Introduced `ruby_rubocop` rule for running rubocop in analysis mode or auto-correcting mode. +* Added an example gem workspace under `examples/example-gem` +* Default ruby used is now 2.7.0. We also now allow 2.7.0 to be built by Bazel. +* Bazelisk has been updated to 1.3.0 +* Updated Bazel version from 2.0.0 to 2.1.0 +* Updated gem versions in the Gemfile +* Changed how the `ruby_bundle` pulls gem's folders into the bundle to include additional files. +* Many other small changes, for full list [please see the diff](https://github.com/bazelruby/rules_ruby/compare/v0.2.0...v0.3.0). + +# 0.2.0 / 2020-12-30 + +[Total Changes since v0.1.0](https://github.com/bazelruby/rules_ruby/compare/v0.1.0...v0.2.0) + +# 0.1.0 / 2019-11-20 + +* Initial migration from [Yugui](https://github.com/yugui) rules ruby. \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index f5e28f7..3080887 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,18 +4,20 @@ GEM ast (2.4.0) jaro_winkler (1.5.4) parallel (1.19.1) - parser (2.6.5.0) + parser (2.7.0.3) ast (~> 2.4.0) rainbow (3.0.0) - rubocop (0.78.0) + rexml (3.2.4) + rubocop (0.80.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 2.7.0.1) rainbow (>= 2.2.2, < 4.0) + rexml ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.1) - unicode-display_width (1.6.0) + unicode-display_width (1.6.1) PLATFORMS ruby @@ -24,4 +26,4 @@ DEPENDENCIES rubocop (~> 0.78) BUNDLED WITH - 2.0.2 + 2.1.2 diff --git a/README.md b/README.md index 7804192..04a9f17 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,20 @@ -* [Status:](#status) +* [Rules Development Status](#rules-development-status) * [Usage](#usage) + * [`WORKSPACE` File](#workspace-file) + * [`BUILD.bazel` files](#buildbazel-files) * [Rules](#rules) - * [ruby_library](#ruby_library) - * [ruby_binary](#ruby_binary) - * [ruby_test](#ruby_test) - * [bundle_install](#bundle_install) + * [`ruby_library`](#ruby_library) + * [`ruby_binary`](#ruby_binary) + * [`ruby_test`](#ruby_test) + * [`ruby_bundle`](#ruby_bundle) + * [Limitations](#limitations) + * [Conventions](#conventions) + * [Example: `WORKSPACE`:](#example-workspace) + * [Example: `lib/BUILD.bazel`:](#example-libbuildbazel) + * [`ruby_rspec`](#ruby_rspec) + * [`ruby_gem`](#ruby_gem) * [What's coming next](#whats-coming-next) * [Contributing](#contributing) * [Setup](#setup) @@ -25,60 +33,121 @@ | Build | Status | |---------: |--------------------------------------------------------------------------------------------------------------------------------------------------- | -| CircleCI Develop: | [![CircleCI](https://circleci.com/gh/bazelruby/rules_ruby/tree/develop.svg?style=svg)](https://circleci.com/gh/bazelruby/rules_ruby/tree/develop) | -| CircleCI Default: | [![CircleCI](https://circleci.com/gh/bazelruby/rules_ruby.svg?style=svg)](https://circleci.com/gh/bazelruby/rules_ruby) | -| Develop: | [![Build Status](https://travis-ci.org/bazelruby/rules_ruby.svg?branch=develop)](https://travis-ci.org/bazelruby/rules_ruby) | -| Master: | [![Build Status](https://travis-ci.org/bazelruby/rules_ruby.svg?branch=master)](https://travis-ci.org/bazelruby/rules_ruby) | +| CircleCI | [![CircleCI](https://circleci.com/gh/bazelruby/rules_ruby/tree/develop.svg?style=svg)](https://circleci.com/gh/bazelruby/rules_ruby/tree/develop) | +| TravisCI | [![Build Status](https://travis-ci.org/bazelruby/rules_ruby.svg?branch=develop)](https://travis-ci.org/bazelruby/rules_ruby) | # Rules Ruby -Ruby rules for [Bazel](https://bazel.build). +This is the README for Ruby Rules for the [Bazel Build](https://bazel.build) system. -## Status: +## Rules Development Status -**Work in progress.** +| **Readiness** | **Types of Applications** | +| :------------------------------------------ | :----------------------------------------------------------- | +| ![Ready](docs/img/status-ready.svg) | ruby apps, ruby gems, micro-services, ideally in a mono-repo | +| ![Wait](docs/img/status-wait.svg) | medium-sized Ruby on Rails apps, ideally in a mono-repo | +| ![Not Ready](docs/img/status-not-ready.svg) | complex Ruby on Rails monoliths, single-repo | -We have a guide on [Building your first Ruby Project](https://github.com/bazelruby/rules_ruby/wiki/Build-your-ruby-project) on the Wiki. We encourage you to check it out. + +Note: we have a short guide on [Building your first Ruby Project](https://github.com/bazelruby/rules_ruby/wiki/Build-your-ruby-project) on the Wiki. We encourage you to check it out. ## Usage -Add `ruby_rules_dependencies` and `ruby_register_toolchains` into your `WORKSPACE` file. +### `WORKSPACE` File + +#### Load dependencies, select Ruby SDK and define one or more Bundles ```python -# To get the latest, grab the 'develop' branch. +workspace(name = "my_ruby_project") + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +bazel_skylib_workspace() + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +#——————————————————————————————————————————————————————————————————————— +# To get the latest ruby rules, grab the 'develop' branch. +#——————————————————————————————————————————————————————————————————————— git_repository( - name = "bazelruby_ruby_rules", + name = "bazelruby_rules_ruby", remote = "https://github.com/bazelruby/rules_ruby.git", branch = "develop", ) load( - "@bazelruby_ruby_rules//ruby:deps.bzl", - "ruby_register_toolchains", - "ruby_rules_dependencies", + "@bazelruby_rules_ruby//ruby:deps.bzl", + "rules_ruby_dependencies", + "rules_ruby_select_sdk", ) -ruby_rules_dependencies() +rules_ruby_dependencies() -ruby_register_toolchains() +#——————————————————————————————————————————————————————————————————————— +# Specify Ruby version — this will either build Ruby or use a local +# RBENV installation if the Ruby version matches. +#——————————————————————————————————————————————————————————————————————— + +rules_ruby_select_sdk(version = "2.7.0") + +#——————————————————————————————————————————————————————————————————————— +# Now, load the ruby_bundle rule & install gems specified in the Gemfile +#——————————————————————————————————————————————————————————————————————— + +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_bundle", +) + +ruby_bundle( + name = "bundle", + excludes = { + "mini_portile": ["test/**/*"], + }, + gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", +) + +# 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", +) ``` -Add `ruby_library`, `ruby_binary` or `ruby_test` into your `BUILD.bazel` files. +### `BUILD.bazel` file(s) + +Any of the project `BUILD` files can now reference any gems included in the `Gemfile` referenced by the `ruby_bundle` rule, and defined in the project's `WORKSPACE` file. + +#### Define Ruby Executable, Library and an RSpec + +Add `ruby_library`, `ruby_binary`, `ruby_rspec` or `ruby_test` into your `BUILD.bazel` files. ```python +#——————————————————————————————————————————————————————————————————————— +# Define Ruby executable, test, spec and package a gem +#——————————————————————————————————————————————————————————————————————— + load( - "@bazelruby_ruby_rules//ruby:defs.bzl", + "@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_binary", "ruby_library", "ruby_test", + "ruby_rspec", ) ruby_library( name = "foo", - srcs = ["lib/foo.rb"], + srcs = glob(["lib/**/*.rb"]), includes = ["lib"], + deps = [ + "@bundle//:activesupport", + "@bundle//:awesome_print", + "@bundle//:rubocop", + ] ) ruby_binary( @@ -88,24 +157,91 @@ ruby_binary( ) ruby_test( - name = "foo_test", + name = "foo-test", srcs = ["test/foo_test.rb"], deps = [":foo"], ) + +ruby_rspec( + name = "foo-spec", + specs = glob(["spec/**/*.rb"]), + rspec_args = { "--format": "progress" }, + deps = [":foo"] +} ``` -## Rules +#### Package Ruby files as a Gem + +Use `ruby_gem` rule to package any number of ruby files or folders into a Ruby-Gem compatible ZIP archive. + +```python +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_gem", +) + +ruby_gem( + name = "awesome-sauce-gem", # name of the build target + gem_name = "awesome-sauce", # name of the gem + gem_version = "0.1.0", + gem_summary = "Example gem to demonstrate Bazel Gem packaging", + gem_description = "Example gem to demonstrate Bazel Gem packaging", + gem_homepage = "https://github.com/bazelruby/rules_ruby", + gem_authors = [ + "BazelRuby", + "Konstantin Gredeskoul" + ], + gem_author_emails = [ + "bazelruby@googlegroups.com", + ], + gem_runtime_dependencies = { + "colored2": "~> 3.1.2", + "hashie": "", + }, + gem_development_dependencies = { + "rspec": "", + "rspec-its": "", + "rubocop": "", + }, + srcs = [ + glob("{bin,exe,lib,spec}/**/*.rb") + ], + deps = [ + "//lib:example_gem", + ], +) + +``` + +### Rule Dependency Diagram + +> NOTE: this diagram is slightly outdated. The following diagram attempts to capture the implementation behind `ruby_library` that depends on the result of `bundle install`, and a `ruby_binary` that depends on both: ![Ruby Rules](docs/img/ruby_rules.png) +## Rules ### `ruby_library` -
-ruby_library(name, deps, srcs, data, compatible_with, deprecation, distribs, features, licenses, restricted_to, tags, testonly, toolchains, visibility)
-
+```python +ruby_library( + name, + deps, + srcs, + data, + compatible_with, + deprecation, + distribs, + features, + licenses, + restricted_to, + tags, + testonly, + toolchains, + visibility) +``` @@ -178,9 +314,27 @@ ruby_library(name, deps, srcs, data, compatible_with, deprecation, distribs, fea ### `ruby_binary` -
-ruby_binary(name, deps, srcs, data, main, compatible_with, deprecation, distribs, features, licenses, restricted_to, tags, testonly, toolchains, visibility, args, output_licenses)
-
+```python +ruby_binary( + name, + deps, + srcs, + data, + main, + compatible_with, + deprecation, + distribs, + features, + licenses, + restricted_to, + tags, + testonly, + toolchains, + visibility, + args, + output_licenses +) +```
@@ -259,9 +413,29 @@ ruby_binary(name, deps, srcs, data, main, compatible_with, deprecation, distribs ### `ruby_test` -
-ruby_test(name, deps, srcs, data, main, compatible_with, deprecation, distribs, features, licenses, restricted_to, tags, testonly, toolchains, visibility, args, size, timeout, flaky, local, shard_count)
-
+```python +ruby_test( + name, + deps, + srcs, + data, + main, + compatible_with, + deprecation, + distribs, + features, + licenses, + restricted_to, + tags, + testonly, + toolchains, + visibility, + args, + size, + timeout, + flaky, + local, shard_count) +```
@@ -338,51 +512,148 @@ ruby_test(name, deps, srcs, data, main, compatible_with, deprecation, distribs,
-### `bundle_install` +### `ruby_bundle` -Installs gems with Bundler, and make them available as a `ruby_library`. +**NOTE: This is a repository rule, and can only be used in a `WORKSPACE` file.** -Example: `WORKSPACE`: +This rule installs gems defined in a Gemfile using Bundler, and exports individual gems from the bundle, as well as the entire bundle, available as a `ruby_library` that can be depended upon from other targets. ```python -git_repository( - name = "bazelruby_ruby_rules", - remote = "https://github.com/bazelruby/rules_ruby.git", - tag = "v0.1.0", +ruby_bundle( + name, + gemfile, + gemfile_lock, + bundler_version = "2.1.2", + excludes = [], + ruby_sdk = "@org_ruby_lang_ruby_toolchain", + ruby_interpreter = "@org_ruby_lang_ruby_toolchain//:ruby", ) +``` -load( - "@bazelruby_ruby_rules//ruby:deps.bzl", - "ruby_register_toolchains", - "ruby_rules_dependencies", -) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes
name + Name, required +

A unique name for this rule.

+
gemfile + Label, required +

+ The Gemfile which Bundler runs with. +

+
gemfile_lock + Label, required +

The Gemfile.lock which Bundler runs with.

+

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

+
bundler_version + String, optional +

The Version of Bundler to use. Defaults to 2.1.2.

+

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

+
+ +#### Limitations + +Installing using a `Gemfile` that uses the `gemspec` keyword is not currently supported. + +#### Conventions -ruby_rules_dependencies() +`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`: -ruby_register_toolchains() + * `@app_bundle//:bundler` — references just the Bundler from the bundle. + * `@app_bundle//:gems` — references *all* gems in the bundle (i.e. "the entire bundle"). + * `@app_bundle//:gem-name` — references *just the specified* gem in the bundle, eg. `@app_bundle//:awesome_print`. + * `@app_bundle//:bin` — references to all installed executables from this bundle, with individual executables accessible via eg. `@app_bundle//:bin/rubocop` -load("@bazelruby_ruby_rules//ruby:defs.bzl", "bundle_install") +#### `WORKSPACE`: -bundle_install( +```python +load("@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_bundle") + +ruby_bundle( name = "gems", + bundler_version = '2.1.2', gemfile = "//:Gemfile", gemfile_lock = "//:Gemfile.lock", ) ``` -Example: `lib/BUILD.bazel`: +#### `BUILD.bazel`: + +```pythonj +# Reference the entire bundle with :gems -```python ruby_library( name = "foo", srcs = ["foo.rb"], - deps = ["@gems//:libs"], + deps = ["@gems//:gems"], +) + +# Or, reference specific gems from the bundle like so: + +ruby_binary( + name = "rubocop", + srcs = [":foo", ".rubocop.yml"], + args = ["-P", "-D", "-c" ".rubocop.yml"], + main = "@gems//:bin/rubocop", + deps = ["@gems//:rubocop"], +) +``` + +### `ruby_rspec` + +```python +ruby_rspec( + name, + deps, + srcs, + data, + main, + rspec_args, + bundle, + compatible_with, + deprecation, + distribs, + features, + licenses, + restricted_to, + tags, + testonly, + toolchains, + visibility, + args, + size, + timeout, + flaky, + local, + shard_count ) ``` -
-bundle_install(name, gemfile, gemfile_lock)
-
@@ -402,22 +673,223 @@ bundle_install(name, gemfile, gemfile_lock) - + - + + + + + + + + + + + + + + + + + + + + + + + +
gemfilesrcs - Label, required + List of Labels, required

- The Gemfile which Bundler runs with. + List of .rb files.

gemfile_lockdeps - Label, required -

The Gemfile.lock which Bundler runs with.

-

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

+ List of labels, optional +

+ List of targets that are required by the srcs Ruby + files. +

main + Label, optional +

The entrypoint file. It must be also in srcs.

+

If not specified, $(NAME).rb where $(NAME) is the name of this rule.

+
rspec_args + List of strings, optional +

Command line arguments to the rspec binary, eg ["--progress", "-p2", "-b"]

+

If not specified, the default arguments defined in `constants.bzl` are used: --format=documentation --force-color.

+
includes + List of strings, optional +

+ List of paths to be added to $LOAD_PATH at runtime. + The paths must be relative to the the workspace which this rule belongs to. +

+
rubyopt + List of strings, optional +

+ List of options to be passed to the Ruby interpreter at runtime. +

+

+ NOTE: -I option should usually go to includes attribute. +

+
And other common attributes
+ +### `ruby_gem` + +Used to generate a zipped gem containing its srcs, dependencies and a gemspec. + +```python +ruby_gem( + name, + gem_name, + gem_version, + gem_summary, + gem_description, + gem_homepage, + gem_authors, + gem_author_emails, + gem_runtime_dependencies, + gem_development_dependencies, + require_paths = ["lib"], + srcs = srcs, + deps = deps, + data = data +) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes
name + Name, required +

A unique name for this build target.

+
gem_name + Name of the gem, required +

The name of the gem to be generated.

+
gem_version + String, optional +

+ The version of the gem. Is used to name the output file, + which becomes name-version.zip, and also + included in the Gemspec. +

+
gem_summary + String, optional +

One line summary of the gem purpose.

+
gem_description + String, required +

Single-line, paragraph-sized description text for the gem.

+
gem_homepage + String, optional +

Homepage URL of the gem.

+
gem_authors + List of Strings, required +

+ List of human readable names of the gem authors. + Required to generate a valid gemspec. +

+
gem_author_emails + List of Strings, optional +

+ List of email addresses of the authors. +

+
srcs + List of Labels, optional +

+ List of .rb files. +

+

At least srcs or deps must be present

+
deps + List of labels, optional +

+ List of targets that are required by the srcs Ruby + files. +

+

At least srcs or deps must be present

+
require_paths + List of Strings, optional +

+ List of paths to be added to the Ruby LOAD_PATH when using this gem. + Typically this value is just `lib` (which is also the default). +

+
gem_runtime_dependencies + String Dictionary, optional +

+ This is a dictionary where keys are gem names, and values are either an empty + string or a gem version specification. + For instance, the pessimistic version specifier ~> 3.0 means that all versions up to 4.0 are accepted. +

+
gem_development_dependencies + String Dictionary, optional +

+ Similar to the above, this specifies gems necessary for the development of the above gem, such as + testing gems, linters, code coverage and more. +

+
@@ -425,7 +897,7 @@ bundle_install(name, gemfile, gemfile_lock) 1. Building native extensions in gems with Bazel 2. Using a specified version of Ruby. -3. Building and releasing your gems with Bazel +3. Releasing your gems with Bazel ## Contributing @@ -556,3 +1028,5 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + diff --git a/VERSION.yml b/VERSION.yml new file mode 100644 index 0000000..f5ab055 --- /dev/null +++ b/VERSION.yml @@ -0,0 +1,5 @@ +rules_ruby: + version: 0.3.0 + + + diff --git a/WORKSPACE b/WORKSPACE index 825f82a..4c1bdf1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,10 +1,8 @@ -workspace(name = "bazelruby_ruby_rules") +workspace(name = "bazelruby_rules_ruby") -load("@//ruby:deps.bzl", "ruby_register_toolchains", "ruby_rules_dependencies") +load("@//ruby:deps.bzl", "rules_ruby_dependencies", "rules_ruby_select_sdk") -ruby_rules_dependencies() - -ruby_register_toolchains() +rules_ruby_dependencies() load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") @@ -12,10 +10,12 @@ bazel_skylib_workspace() load("@bazel_skylib//lib:versions.bzl", "versions") -versions.check("1.2.1") +versions.check("2.1.1") + +rules_ruby_select_sdk("2.7.0") local_repository( - name = "bazelruby_ruby_rules_ruby_tests_testdata_another_workspace", + name = "bazelruby_rules_ruby_ruby_tests_testdata_another_workspace", path = "ruby/tests/testdata/another_workspace", ) @@ -91,14 +91,18 @@ container_pull( repository = "library/ruby", ) -load("@bazelruby_ruby_rules//ruby:defs.bzl", "bundle_install") +load("@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_bundle") -bundle_install( +ruby_bundle( name = "bundle", + bundler_version = "2.1.2", excludes = { "mini_portile": ["test/**/*"], }, gemfile = "//:Gemfile", gemfile_lock = "//:Gemfile.lock", - version = "2.0.2", ) + +load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") + +rules_pkg_dependencies() diff --git a/bin/deps b/bin/deps index fc36cbd..9244cc6 100755 --- a/bin/deps +++ b/bin/deps @@ -27,7 +27,7 @@ __version::detect() { # Application Constants export RulesRuby__RulesVersion=$(__version::detect .rules_version) -export RulesRuby__RubyVersion=$(__version::detect .ruby_version) +export RulesRuby__RubyVersion=2.7.0 export RulesRuby__BazelVersion=$(__version::detect .bazelversion) bazel-sha() { diff --git a/bin/linter b/bin/linter index d44ae69..fa7ddfa 100755 --- a/bin/linter +++ b/bin/linter @@ -104,7 +104,7 @@ lint::all() { hr echo info "ACTION: ${bldylw}Please add any respective files to the commit and retry." - retrn 1 + return 1 else echo success "No changes detected, changes passed linter inspection." diff --git a/bin/test-suite b/bin/test-suite index abaf6c2..108b884 100755 --- a/bin/test-suite +++ b/bin/test-suite @@ -1,5 +1,5 @@ #!/usr/bin/env bash - +# vim: ft=bash set -e # shellcheck disable=SC1091 @@ -10,9 +10,9 @@ export BUNDLE_PATH="${BUNDLE_PATH:-${HOME}/.bundle/gems}" export PATH="${HOME}/.rbenv/bin:${HOME}/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin:/opt/local/bin" export BAZEL_OPTS="--host_jvm_args=-Xmx500m --host_jvm_args=-Xms500m" -export BAZEL_BUILD_OPTS="--curses=no --verbose_failures -j 15 --show_progress_rate_limit 0 " -export BAZEL_TEST_OPTS="--verbose_failures --test_output=streamed --test_verbose_timeout_warnings " -export RUBY_VERSION=$(cat .ruby-version) +export BAZEL_BUILD_OPTS="--curses=no --verbose_failures -j 30 --progress_report_interval=2" +export BAZEL_TEST_OPTS="--verbose_failures --test_verbose_timeout_warnings --verbose_explanations" +export RUBY_VERSION=2.7.0 export BashMatic__Expr=" [[ -f ${SHELL_INIT} ]] && source ${SHELL_INIT} @@ -147,8 +147,17 @@ test::buildifier() { test::simple-script() { __test::exec simple-script " cd examples/simple_script - ${Bazel__BuildSteps} - bazel ${BAZEL_OPTS} run ${BAZEL_BUILD_OPTS} -- :rubocop ; + ${Bazel__BuildSteps} + echo run :bin; bazel ${BAZEL_OPTS} run ${BAZEL_BUILD_OPTS} :bin + echo run :rubocop; bazel ${BAZEL_OPTS} run ${BAZEL_BUILD_OPTS} :rubocop + " +} + +# Builds and runs workspace inside examples/simple_script +test::example-gem() { + __test::exec example-gem " + cd examples/example_gem + echo bazel ${BAZEL_OPTS} build ...:all; bazel ${BAZEL_OPTS} build ...:all " } @@ -178,9 +187,10 @@ test::all() { test::bazel-info test::workspace test::simple-script - test::buildifier + test::example-gem test::rubocop test::rspec + test::buildifier } test-suite::setup() { diff --git a/docs/img/status-not-ready.svg b/docs/img/status-not-ready.svg new file mode 100644 index 0000000..3a86de9 --- /dev/null +++ b/docs/img/status-not-ready.svg @@ -0,0 +1 @@ + Development StatusDevelopment StatusNot ReadyNot Ready \ No newline at end of file diff --git a/docs/img/status-ready.svg b/docs/img/status-ready.svg new file mode 100644 index 0000000..fcb75e3 --- /dev/null +++ b/docs/img/status-ready.svg @@ -0,0 +1 @@ + Development StatusDevelopment StatusReadyReady \ No newline at end of file diff --git a/docs/img/status-wait.svg b/docs/img/status-wait.svg new file mode 100644 index 0000000..5bd705b --- /dev/null +++ b/docs/img/status-wait.svg @@ -0,0 +1 @@ + Development StatusDevelopment StatusWaitWait \ No newline at end of file diff --git a/examples/example_gem/BUILD.bazel b/examples/example_gem/BUILD.bazel new file mode 100644 index 0000000..55948ee --- /dev/null +++ b/examples/example_gem/BUILD.bazel @@ -0,0 +1,35 @@ +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_gem", +) + +package(default_visibility = ["//:__subpackages__"]) + +ruby_gem( + name = "example_gem", + srcs = [ + "//lib:example_gem", + ], + gem_author_emails = [ + "dev@coinbase.com", + "bazelruby@googlegroups.com", + ], + gem_authors = [ + "Coinbase", + "BazelRuby", + ], + gem_description = "Example gem to demonstrate Bazel Gem packaging", + gem_development_dependencies = { + "rspec": "", + "rspec-its": "", + "rubocop": "", + }, + gem_homepage = "https://github.com/bazelruby/rules_ruby", + gem_name = "example-gem", + gem_runtime_dependencies = { + "colored2": "", + "hashie": "", + }, + gem_summary = "Example gem to demonstrate Bazel Gem packaging", + gem_version = "0.1.0", +) diff --git a/examples/example_gem/WORKSPACE b/examples/example_gem/WORKSPACE new file mode 100644 index 0000000..2fee5d4 --- /dev/null +++ b/examples/example_gem/WORKSPACE @@ -0,0 +1,22 @@ +workspace(name = "bazelruby_rules_ruby_example_gem") + +# Importing rules_ruby from the parent directory for developing +# rules_ruby itself... +local_repository( + name = "bazelruby_rules_ruby", + path = "../..", +) + +load( + "@bazelruby_rules_ruby//ruby:deps.bzl", + "rules_ruby_dependencies", + "rules_ruby_select_sdk", +) + +rules_ruby_dependencies() + +rules_ruby_select_sdk("2.7.0") + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() diff --git a/examples/example_gem/lib/BUILD b/examples/example_gem/lib/BUILD new file mode 100644 index 0000000..3239452 --- /dev/null +++ b/examples/example_gem/lib/BUILD @@ -0,0 +1,12 @@ +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_library", +) + +package(default_visibility = ["//:__subpackages__"]) + +ruby_library( + name = "example_gem", + srcs = ["example_gem.rb"], + deps = ["//lib/foo:default_library"], +) diff --git a/examples/example_gem/lib/example_gem.rb b/examples/example_gem/lib/example_gem.rb new file mode 100644 index 0000000..f41ee6f --- /dev/null +++ b/examples/example_gem/lib/example_gem.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require_relative 'foo/bar' + +puts 'Foo' diff --git a/examples/example_gem/lib/foo/BUILD b/examples/example_gem/lib/foo/BUILD new file mode 100644 index 0000000..9e6e2fe --- /dev/null +++ b/examples/example_gem/lib/foo/BUILD @@ -0,0 +1,11 @@ +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_library", +) + +package(default_visibility = ["//:__subpackages__"]) + +ruby_library( + name = "default_library", + srcs = ["bar.rb"], +) diff --git a/examples/example_gem/lib/foo/bar.rb b/examples/example_gem/lib/foo/bar.rb new file mode 100644 index 0000000..6c6db65 --- /dev/null +++ b/examples/example_gem/lib/foo/bar.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Foo + class Bar + def call + puts 'Hello World' + end + end +end diff --git a/examples/simple_rails_api/.ruby-version b/examples/simple_rails_api/.ruby-version index 57cf282..24ba9a3 100644 --- a/examples/simple_rails_api/.ruby-version +++ b/examples/simple_rails_api/.ruby-version @@ -1 +1 @@ -2.6.5 +2.7.0 diff --git a/examples/simple_rails_api/BUILD b/examples/simple_rails_api/BUILD index 686a8a0..c53e54b 100644 --- a/examples/simple_rails_api/BUILD +++ b/examples/simple_rails_api/BUILD @@ -1,11 +1,10 @@ -package(default_visibility = ["//:__subpackages__"]) - load( - "@bazelruby_ruby_rules//ruby:defs.bzl", + "@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_binary", - "ruby_test", ) +package(default_visibility = ["//:__subpackages__"]) + ruby_binary( name = "server", srcs = glob( diff --git a/examples/simple_rails_api/Gemfile b/examples/simple_rails_api/Gemfile index af10772..8f98992 100644 --- a/examples/simple_rails_api/Gemfile +++ b/examples/simple_rails_api/Gemfile @@ -1,8 +1,6 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.6.5' - # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.2' # Use sqlite3 as the database for Active Record diff --git a/examples/simple_rails_api/Gemfile.lock b/examples/simple_rails_api/Gemfile.lock index 1432e0b..c89e612 100644 --- a/examples/simple_rails_api/Gemfile.lock +++ b/examples/simple_rails_api/Gemfile.lock @@ -1,72 +1,72 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.0.2) - actionpack (= 6.0.2) + actioncable (6.0.2.1) + actionpack (= 6.0.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.2) - actionpack (= 6.0.2) - activejob (= 6.0.2) - activerecord (= 6.0.2) - activestorage (= 6.0.2) - activesupport (= 6.0.2) + actionmailbox (6.0.2.1) + actionpack (= 6.0.2.1) + activejob (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) mail (>= 2.7.1) - actionmailer (6.0.2) - actionpack (= 6.0.2) - actionview (= 6.0.2) - activejob (= 6.0.2) + actionmailer (6.0.2.1) + actionpack (= 6.0.2.1) + actionview (= 6.0.2.1) + activejob (= 6.0.2.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.2) - actionview (= 6.0.2) - activesupport (= 6.0.2) - rack (~> 2.0) + actionpack (6.0.2.1) + actionview (= 6.0.2.1) + activesupport (= 6.0.2.1) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.2) - actionpack (= 6.0.2) - activerecord (= 6.0.2) - activestorage (= 6.0.2) - activesupport (= 6.0.2) + actiontext (6.0.2.1) + actionpack (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) nokogiri (>= 1.8.5) - actionview (6.0.2) - activesupport (= 6.0.2) + actionview (6.0.2.1) + activesupport (= 6.0.2.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.2) - activesupport (= 6.0.2) + activejob (6.0.2.1) + activesupport (= 6.0.2.1) globalid (>= 0.3.6) - activemodel (6.0.2) - activesupport (= 6.0.2) - activerecord (6.0.2) - activemodel (= 6.0.2) - activesupport (= 6.0.2) - activestorage (6.0.2) - actionpack (= 6.0.2) - activejob (= 6.0.2) - activerecord (= 6.0.2) + activemodel (6.0.2.1) + activesupport (= 6.0.2.1) + activerecord (6.0.2.1) + activemodel (= 6.0.2.1) + activesupport (= 6.0.2.1) + activestorage (6.0.2.1) + actionpack (= 6.0.2.1) + activejob (= 6.0.2.1) + activerecord (= 6.0.2.1) marcel (~> 0.3.1) - activesupport (6.0.2) + activesupport (6.0.2.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2) - bootsnap (1.4.5) + bootsnap (1.4.6) msgpack (~> 1.0) builder (3.2.4) - byebug (11.0.1) - concurrent-ruby (1.1.5) - crass (1.0.5) + byebug (11.1.1) + concurrent-ruby (1.1.6) + crass (1.0.6) erubi (1.9.0) - ffi (1.11.3) + ffi (1.12.2) globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.7.0) + i18n (1.8.2) concurrent-ruby (~> 1.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) @@ -80,48 +80,48 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) method_source (0.9.2) - mimemagic (0.3.3) + mimemagic (0.3.4) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.13.0) - msgpack (1.3.1) + minitest (5.14.0) + msgpack (1.3.3) nio4r (2.5.2) - nokogiri (1.10.7) + nokogiri (1.10.8) mini_portile2 (~> 2.4.0) puma (4.3.1) nio4r (~> 2.0) - rack (2.0.8) + rack (2.2.2) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.2) - actioncable (= 6.0.2) - actionmailbox (= 6.0.2) - actionmailer (= 6.0.2) - actionpack (= 6.0.2) - actiontext (= 6.0.2) - actionview (= 6.0.2) - activejob (= 6.0.2) - activemodel (= 6.0.2) - activerecord (= 6.0.2) - activestorage (= 6.0.2) - activesupport (= 6.0.2) + rails (6.0.2.1) + actioncable (= 6.0.2.1) + actionmailbox (= 6.0.2.1) + actionmailer (= 6.0.2.1) + actionpack (= 6.0.2.1) + actiontext (= 6.0.2.1) + actionview (= 6.0.2.1) + activejob (= 6.0.2.1) + activemodel (= 6.0.2.1) + activerecord (= 6.0.2.1) + activestorage (= 6.0.2.1) + activesupport (= 6.0.2.1) bundler (>= 1.3.0) - railties (= 6.0.2) + railties (= 6.0.2.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.2) - actionpack (= 6.0.2) - activesupport (= 6.0.2) + railties (6.0.2.1) + actionpack (= 6.0.2.1) + activesupport (= 6.0.2.1) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) rake (13.0.1) rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rb-inotify (0.10.1) ffi (~> 1.0) ruby_dep (1.5.0) spring (2.1.0) @@ -135,10 +135,10 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.4.1) - thor (1.0.0) + sqlite3 (1.4.2) + thor (1.0.1) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.6) thread_safe (~> 0.1) websocket-driver (0.7.1) websocket-extensions (>= 0.1.0) @@ -159,8 +159,5 @@ DEPENDENCIES sqlite3 (~> 1.4) tzinfo-data -RUBY VERSION - ruby 2.6.5p114 - BUNDLED WITH - 2.0.2 + 2.1.2 diff --git a/examples/simple_rails_api/WORKSPACE b/examples/simple_rails_api/WORKSPACE index d4e4f2c..52d2999 100644 --- a/examples/simple_rails_api/WORKSPACE +++ b/examples/simple_rails_api/WORKSPACE @@ -1,29 +1,29 @@ -workspace(name = "bazelruby_ruby_rules_example") +workspace(name = "bazelruby_rules_ruby_example") # Importing rules_ruby from the parent directory for developing # rules_ruby itself... local_repository( - name = "bazelruby_ruby_rules", + name = "bazelruby_rules_ruby", path = "../..", ) load( - "@bazelruby_ruby_rules//ruby:deps.bzl", - "ruby_register_toolchains", - "ruby_rules_dependencies", + "@bazelruby_rules_ruby//ruby:deps.bzl", + "rules_ruby_dependencies", + "rules_ruby_select_sdk", ) -ruby_rules_dependencies() +rules_ruby_dependencies() -ruby_register_toolchains(version = "2.6.5") +rules_ruby_select_sdk(version = "2.7.0") load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace() -load("@bazelruby_ruby_rules//ruby:defs.bzl", "bundle_install") +load("@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_bundle") -bundle_install( +ruby_bundle( name = "bundle", gemfile = "//:Gemfile", gemfile_lock = "//:Gemfile.lock", diff --git a/examples/simple_script/.rubocop.yml b/examples/simple_script/.rubocop.yml index 461aed5..7b653d9 100644 --- a/examples/simple_script/.rubocop.yml +++ b/examples/simple_script/.rubocop.yml @@ -1,11 +1,36 @@ inherit_from: .relaxed-rubocop-2.4.yml AllCops: - TargetRubyVersion: 2.6.5 + TargetRubyVersion: 2.6 + UseCache: true + DefaultFormatter: progress + DisplayStyleGuide: true + DisplayCopNames: true Exclude: - - rubocop - - bazel-*/**/* - - external/**/* + - "external*/**/*" + - "bazel-*/**/*" + - "**/examples/**/*" + - "**/BUILD" + - "**/*.bazel" + - "**/*.bzl" + - "**/rubocop" - "**/vendor/bundle/**/*" + Include: + - '**/*.rb' + - '**/*.gemfile' + - '**/*.gemspec' + - '**/*.rake' + - '**/*.ru' + - '**/Gemfile' + - '**/Rakefile' +Layout/HashAlignment: + Enabled: true + EnforcedColonStyle: table + +Style/Dir: + Enabled: false +# In Bazel we want to use __FILE__ because __dir__points to the actual sources +Style/ExpandPathArguments: + Enabled: false diff --git a/examples/simple_script/.ruby-version b/examples/simple_script/.ruby-version deleted file mode 100644 index 6816713..0000000 --- a/examples/simple_script/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.6.5 \ No newline at end of file diff --git a/examples/simple_script/BUILD b/examples/simple_script/BUILD deleted file mode 100644 index 7500d6d..0000000 --- a/examples/simple_script/BUILD +++ /dev/null @@ -1,55 +0,0 @@ -package(default_visibility = ["//:__subpackages__"]) - -load( - "@bazelruby_ruby_rules//ruby:defs.bzl", - "ruby_binary", - "ruby_test", -) - -ruby_binary( - name = "default_bin", - srcs = ["script.rb"], - main = "script.rb", - deps = [ - "//lib:foo", - "@bundle//:awesome_print", - ], -) - -ruby_test( - name = "script_spec", - timeout = "short", - srcs = [ - "script.rb", - "spec/script_spec.rb", - ], - main = "spec/script_spec.rb", - rubyopt = ["-rrspec/autorun"], # require autorun because it is needed - deps = [ - "//lib:foo", - "@bundle//:awesome_print", - "@bundle//:rspec", - ], -) - -# TODO: not the best way of defining this, should make `rubocop` rule like `buildifier` -ruby_binary( - name = "rubocop", - srcs = [ - ".relaxed-rubocop-2.4.yml", - ".rubocop.yml", - "script.rb", - "spec/script_spec.rb", - "@bundle//:bin/rubocop", - ], - args = [ - "-c", - ".rubocop.yml", - "-P", - "-D", - ], - main = "@bundle//:bin/rubocop", - deps = [ - "@bundle//:rubocop", - ], -) diff --git a/examples/simple_script/BUILD.bazel b/examples/simple_script/BUILD.bazel new file mode 100644 index 0000000..bf0e803 --- /dev/null +++ b/examples/simple_script/BUILD.bazel @@ -0,0 +1,96 @@ +load( + "@bazelruby_rules_ruby//ruby:defs.bzl", + "ruby_binary", + "ruby_rspec", + "ruby_rubocop", + "ruby_test", +) + +package(default_visibility = ["//:__subpackages__"]) + +ruby_binary( + name = "bin", + srcs = ["script.rb"], + main = "script.rb", + deps = [ + "//lib:foo", + "@bundle//:awesome_print", + ], +) + +# This is an example of the RSpec definition that uses autorun +# and points to spec_helper as the main spec file. It specifies +# which specs to run using the args. + +ruby_test( + name = "all-specs", + timeout = "short", + srcs = [ + "script.rb", + "//lib:foo", + ] + glob([ + "spec/**/*.rb", + ]), + args = [ + "spec", + ], + main = "@bundle//:bin/rspec", + deps = [ + "@bundle//:awesome_print", + "@bundle//:bin", + "@bundle//:rspec", + "@bundle//:rspec-its", + ], +) + +# Finally, this is the short version of the same thing, expressed +# via the ruby_rspec_test rule that does what the above example +# shows but encapsulated in the rule itself. It adds rspec and rspec-its +# gems to the dependency list, executes bin/rspec and passes spec_targets +# as arguments to rspec. +ruby_rspec( + name = "ruby-rspec-test", + srcs = [ + "script.rb", + "//lib:foo", + ], + rspec_args = { + # NOTE: the output is only visible with --test_output=streamed flag + "--format": "progress", # this is how we can override rspec output format + }, + specs = glob([ + "spec/**/*.rb", + ]), + deps = [ + "@bundle//:awesome_print", + ], +) + +ruby_binary( + name = "rubocop-bin", + srcs = [ + "script.rb", + "//lib:foo", + ] + glob([ + "spec/**/*.rb", + ]), + args = [ + "-- *.rb spec/*.rb lib/*.rb -a", + ], + main = "@bundle//:bin/rubocop", + deps = [ + "//lib:foo", + "@bundle//:bin", + ], +) + +# Rubocop rule +# To check +# bazel run rubocop -- -a +ruby_rubocop( + name = "rubocop", + bin = "@bundle//:bin/rubocop", + deps = [ + "@bundle//:rubocop", + ], +) diff --git a/examples/simple_script/Gemfile b/examples/simple_script/Gemfile index 912b976..d677fb5 100644 --- a/examples/simple_script/Gemfile +++ b/examples/simple_script/Gemfile @@ -5,4 +5,5 @@ source 'https://rubygems.org' gem 'awesome_print' gem 'colored2' gem 'rspec', '~> 3.7.0' +gem 'rspec-its' gem 'rubocop', '~> 0.78.0' diff --git a/examples/simple_script/Gemfile.lock b/examples/simple_script/Gemfile.lock index 5c2bd87..c2b42fd 100644 --- a/examples/simple_script/Gemfile.lock +++ b/examples/simple_script/Gemfile.lock @@ -7,7 +7,7 @@ GEM diff-lcs (1.3) jaro_winkler (1.5.4) parallel (1.19.1) - parser (2.6.5.0) + parser (2.7.0.3) ast (~> 2.4.0) rainbow (3.0.0) rspec (3.7.0) @@ -19,6 +19,9 @@ GEM rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) + rspec-its (1.3.0) + rspec-core (>= 3.0.0) + rspec-expectations (>= 3.0.0) rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) @@ -31,7 +34,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.1) - unicode-display_width (1.6.0) + unicode-display_width (1.6.1) PLATFORMS ruby @@ -40,7 +43,8 @@ DEPENDENCIES awesome_print colored2 rspec (~> 3.7.0) + rspec-its rubocop (~> 0.78.0) BUNDLED WITH - 2.1.0 + 2.1.2 diff --git a/examples/simple_script/README.md b/examples/simple_script/README.md index d4d2040..edc2820 100644 --- a/examples/simple_script/README.md +++ b/examples/simple_script/README.md @@ -15,5 +15,5 @@ bundle lock --update Run rubocop with: ``` -bazel run :rubocop -- $(pwd)/* -a +bazel run //:rubocop ``` \ No newline at end of file diff --git a/examples/simple_script/WORKSPACE b/examples/simple_script/WORKSPACE index ecb83d2..e99b80a 100644 --- a/examples/simple_script/WORKSPACE +++ b/examples/simple_script/WORKSPACE @@ -1,34 +1,30 @@ -workspace(name = "bazelruby_ruby_rules_example") +workspace(name = "bazelruby_rules_ruby_example") # Importing rules_ruby from the parent directory for developing # rules_ruby itself... local_repository( - name = "bazelruby_ruby_rules", + name = "bazelruby_rules_ruby", path = "../..", ) load( - "@bazelruby_ruby_rules//ruby:deps.bzl", - "ruby_register_toolchains", - "ruby_rules_dependencies", + "@bazelruby_rules_ruby//ruby:deps.bzl", + "rules_ruby_dependencies", + "rules_ruby_select_sdk", ) -ruby_rules_dependencies() +rules_ruby_dependencies() -ruby_register_toolchains(version = "2.6.5") +rules_ruby_select_sdk(version = "2.7.0") -load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +load("@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_bundle") -bazel_skylib_workspace() - -load("@bazelruby_ruby_rules//ruby:defs.bzl", "bundle_install") - -bundle_install( +ruby_bundle( name = "bundle", + bundler_version = "2.1.2", excludes = { "mini_portile": ["test/**/*"], }, gemfile = "//:Gemfile", gemfile_lock = "//:Gemfile.lock", - version = "2.0.2", ) diff --git a/examples/simple_script/lib/BUILD b/examples/simple_script/lib/BUILD index 46da775..f125ff8 100644 --- a/examples/simple_script/lib/BUILD +++ b/examples/simple_script/lib/BUILD @@ -1,10 +1,10 @@ -package(default_visibility = ["//:__subpackages__"]) - load( - "@bazelruby_ruby_rules//ruby:defs.bzl", + "@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_library", ) +package(default_visibility = ["//:__subpackages__"]) + ruby_library( name = "foo", srcs = ["foo.rb"], diff --git a/examples/simple_script/lib/foo.rb b/examples/simple_script/lib/foo.rb index f4e6243..eff4466 100644 --- a/examples/simple_script/lib/foo.rb +++ b/examples/simple_script/lib/foo.rb @@ -1,7 +1,30 @@ # frozen_string_literal: true -module Foo - def self.aha - "aha" +class Foo + class << self + def yell_aha + puts aha + end + + def aha + 'You said, aha?' + end + + def rot13(value) + return nil unless value.is_a?(String) + + value.tr('abcdefghijklmnopqrstuvwxyz', 'nopqrstuvwxyzabcdefghijklm') + end + end + + attr_reader :goo, :foo + + def initialize(goo) + @goo = goo + @foo = transform(goo) + end + + def transform(incoming = goo) + Foo.rot13(incoming) end end diff --git a/examples/simple_script/script.rb b/examples/simple_script/script.rb index bc1e0ac..308391e 100644 --- a/examples/simple_script/script.rb +++ b/examples/simple_script/script.rb @@ -1,15 +1,12 @@ # frozen_string_literal: true require 'openssl' -require 'lib/foo' require 'awesome_print' +require_relative 'lib/foo' + def oss_rand OpenSSL::BN.rand(512).to_s end puts Foo.aha + ' ' + oss_rand - -puts $LOAD_PATH - -ap Class diff --git a/examples/simple_script/spec/lib/foo_spec.rb b/examples/simple_script/spec/lib/foo_spec.rb new file mode 100644 index 0000000..d65f333 --- /dev/null +++ b/examples/simple_script/spec/lib/foo_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative '../spec_helper' +require_relative '../../lib/foo' + +RSpec.describe Foo do + let(:goo) { 'Green slime was dripping down his throat into his lapdomen...' } + subject(:foo) { Foo.new(goo) } + + context 'without the aha' do + before { allow(Foo).to receive(:yell_aha).and_return('tiny dongle') } + + its(:goo) { should eq goo } + its(:transform) { should_not eq goo } + + # Some rot13 old school encryption :) + its(:transform) { should eq 'Gerra fyvzr jnf qevccvat qbja uvf guebng vagb uvf yncqbzra...' } + end + + context 'aha' do + it 'should print aha' do + expect(Foo).to receive(:puts).with('You said, aha?').and_return(nil) + Foo.yell_aha + end + end +end diff --git a/examples/simple_script/spec/script_spec.rb b/examples/simple_script/spec/script_spec.rb index cb0de75..458b928 100644 --- a/examples/simple_script/spec/script_spec.rb +++ b/examples/simple_script/spec/script_spec.rb @@ -1,36 +1,7 @@ # frozen_string_literal: true -RSpec.configure do |config| - config.expect_with :rspec do |expectations| - expectations.include_chain_clauses_in_custom_matcher_descriptions = true - end - - config.mock_with :rspec do |mocks| - mocks.verify_partial_doubles = true - end - - config.shared_context_metadata_behavior = :apply_to_host_groups - - config.warnings = true - config.filter_run_when_matching :focus - # config.disable_monkey_patching! - config.order = :random - Kernel.srand config.seed -end - -# Sets HOME here because: -# If otherwise, it causes a runtime failure with the following steps. -# 1. RSpec::Core::ConfigurationOptions.global_options_file raises an exception -# because $HOME is not set in the sandbox environment of Bazel -# 2. the rescue clause calls RSpec::Support.#warning -# 3. #warning calls #warn_with -# 4. #warn_with tries to lookup the first caller which is not a part of RSpec. -# But all the call stack entires are about RSpec at this time because -# it is invoked by rpsec/autorun. So #warn_with raises an exception -# 5. The process fails with an unhandled exception. -ENV['HOME'] ||= '/' - -require_relative '../script' +require 'spec_helper' +require 'script' describe 'oss_rand' do it 'generates a String' do diff --git a/examples/simple_script/spec/spec_helper.rb b/examples/simple_script/spec/spec_helper.rb new file mode 100644 index 0000000..65d24b0 --- /dev/null +++ b/examples/simple_script/spec/spec_helper.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Sets HOME here because: +# If otherwise, it causes a runtime failure with the following steps. +# 1. RSpec::Core::ConfigurationOptions.global_options_file raises an exception +# because $HOME is not set in the sandbox environment of Bazel +# 2. the rescue clause calls RSpec::Support.#warning +# 3. #warning calls #warn_with +# 4. #warn_with tries to lookup the first caller which is not a part of RSpec. +# But all the call stack entires are about RSpec at this time because +# it is invoked by rpsec/autorun. So #warn_with raises an exception +# 5. The process fails with an unhandled exception. + +ENV['HOME'] ||= '/' + +require 'rspec' +require 'rspec/its' +require 'awesome_print' +require 'colored2' + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + + config.warnings = true + config.filter_run_when_matching :focus + # config.disable_monkey_patching! + config.order = :random + Kernel.srand config.seed +end diff --git a/ruby/defs.bzl b/ruby/defs.bzl index ec111b8..1777d38 100644 --- a/ruby/defs.bzl +++ b/ruby/defs.bzl @@ -1,23 +1,44 @@ load( - "@bazelruby_ruby_rules//ruby/private:toolchain.bzl", + "@bazelruby_rules_ruby//ruby/private:toolchain.bzl", _toolchain = "ruby_toolchain", ) load( - "@bazelruby_ruby_rules//ruby/private:library.bzl", + "@bazelruby_rules_ruby//ruby/private:library.bzl", _library = "ruby_library", ) load( - "@bazelruby_ruby_rules//ruby/private:binary.bzl", + "@bazelruby_rules_ruby//ruby/private:binary.bzl", _binary = "ruby_binary", _test = "ruby_test", ) load( - "@bazelruby_ruby_rules//ruby/private:bundle.bzl", - _bundle_install = "bundle_install", + "@bazelruby_rules_ruby//ruby/private/bundle:def.bzl", + _bundle = "bundle_install", + _ruby_bundle = "ruby_bundle_install", +) +load( + "@bazelruby_rules_ruby//ruby/private:rspec.bzl", + _rspec = "ruby_rspec", + _rspec_test = "ruby_rspec_test", +) +load( + "@bazelruby_rules_ruby//ruby/private/rubocop:def.bzl", + _rubocop = "rubocop", +) +load( + "@bazelruby_rules_ruby//ruby/private/gemspec:def.bzl", + _gem = "gem", + _gemspec = "gemspec", ) ruby_toolchain = _toolchain ruby_library = _library ruby_binary = _binary ruby_test = _test -bundle_install = _bundle_install +ruby_rspec_test = _rspec_test +ruby_rspec = _rspec +ruby_bundle = _ruby_bundle +ruby_bundle_install = _bundle +ruby_rubocop = _rubocop +ruby_gemspec = _gemspec +ruby_gem = _gem diff --git a/ruby/deps.bzl b/ruby/deps.bzl index 4623094..3a7c45b 100644 --- a/ruby/deps.bzl +++ b/ruby/deps.bzl @@ -1,12 +1,12 @@ # Repository rules load( - "@bazelruby_ruby_rules//ruby/private:dependencies.bzl", - _rules_dependencies = "ruby_rules_dependencies", + "@bazelruby_rules_ruby//ruby/private:dependencies.bzl", + _rules_ruby_dependencies = "rules_ruby_dependencies", ) load( - "@bazelruby_ruby_rules//ruby/private:sdk.bzl", - _register_toolchains = "ruby_register_toolchains", + "@bazelruby_rules_ruby//ruby/private:sdk.bzl", + _rules_ruby_select_sdk = "rules_ruby_select_sdk", ) -ruby_rules_dependencies = _rules_dependencies -ruby_register_toolchains = _register_toolchains +rules_ruby_dependencies = _rules_ruby_dependencies +rules_ruby_select_sdk = _rules_ruby_select_sdk diff --git a/ruby/private/BUILD.bazel b/ruby/private/BUILD.bazel index 4353062..1c16e04 100644 --- a/ruby/private/BUILD.bazel +++ b/ruby/private/BUILD.bazel @@ -1,4 +1,6 @@ exports_files( - ["binary_wrapper.tpl"], + [ + "binary_wrapper.tpl", + ], visibility = ["//visibility:public"], ) diff --git a/ruby/private/binary.bzl b/ruby/private/binary.bzl index 1f20d4a..4a38054 100644 --- a/ruby/private/binary.bzl +++ b/ruby/private/binary.bzl @@ -1,5 +1,4 @@ -load(":constants.bzl", "TOOLCHAIN_TYPE_NAME") -load(":providers.bzl", "RubyLibrary") +load(":constants.bzl", "RUBY_ATTRS", "TOOLCHAIN_TYPE_NAME") load( "//ruby/private/tools:deps.bzl", _transitive_deps = "transitive_deps", @@ -11,14 +10,30 @@ def _to_manifest_path(ctx, file): else: return ("%s/%s" % (ctx.workspace_name, file.short_path)) -def _ruby_binary_impl(ctx): +def _get_gem_path(incpaths): + """ + incpaths is a list of `/lib/ruby//gems/-/lib` + The gem_path is `/lib/ruby/` so we can go from an incpath to the + gem_path pretty easily without much additional work. + """ + if len(incpaths) == 0: + return "" + incpath = incpaths[0] + return incpath.rsplit("/", 3)[0] + +# Having this function allows us to override otherwise frozen attributes +# such as main, srcs and deps. We use this in ruby_rspec_test rule by +# adding rspec as a main, and sources, and rspec gem as a dependency. +# +# There could be similar situations in the future where we might want +# to create a rule (eg, rubocop) that does exactly the same. +def ruby_binary_macro(ctx, main, srcs): sdk = ctx.toolchains[TOOLCHAIN_TYPE_NAME].ruby_runtime interpreter = sdk.interpreter[DefaultInfo].files_to_run.executable - main = ctx.file.main if not main: expected_name = "%s.rb" % ctx.attr.name - for f in ctx.attr.srcs: + for f in srcs: if f.label.name == expected_name: main = f.files.to_list()[0] break @@ -30,12 +45,17 @@ def _ruby_binary_impl(ctx): ) executable = ctx.actions.declare_file(ctx.attr.name) + deps = _transitive_deps( ctx, extra_files = [executable], extra_deps = ctx.attr._misc_deps, ) + gem_path = _get_gem_path(deps.incpaths.to_list()) + + gems_to_pristine = ctx.attr.force_gem_pristine + rubyopt = reversed(deps.rubyopt.to_list()) ctx.actions.expand_template( @@ -46,50 +66,36 @@ def _ruby_binary_impl(ctx): "{rubyopt}": repr(rubyopt), "{main}": repr(_to_manifest_path(ctx, main)), "{interpreter}": _to_manifest_path(ctx, interpreter), + "{gem_path}": gem_path, + "{should_gem_pristine}": str(len(gems_to_pristine) > 0).lower(), + "{gems_to_pristine}": " ".join(gems_to_pristine), }, ) - return [DefaultInfo( + info = DefaultInfo( executable = executable, - default_runfiles = deps.default_files, - data_runfiles = deps.data_files, - )] + runfiles = deps.default_files.merge(deps.data_files), + ) + + return [info] -_ATTRS = { - "srcs": attr.label_list( - allow_files = True, - ), - "deps": attr.label_list( - providers = [RubyLibrary], - ), - "includes": attr.string_list(), - "rubyopt": attr.string_list(), - "data": attr.label_list( - allow_files = True, - ), - "main": attr.label( - allow_single_file = True, - ), - "_wrapper_template": attr.label( - allow_single_file = True, - default = "binary_wrapper.tpl", - ), - "_misc_deps": attr.label_list( - allow_files = True, - default = ["@bazel_tools//tools/bash/runfiles"], - ), -} +def ruby_binary_impl(ctx): + return ruby_binary_macro( + ctx, + ctx.file.main, + ctx.attr.srcs, + ) ruby_binary = rule( - implementation = _ruby_binary_impl, - attrs = _ATTRS, + implementation = ruby_binary_impl, + attrs = RUBY_ATTRS, executable = True, toolchains = [TOOLCHAIN_TYPE_NAME], ) ruby_test = rule( - implementation = _ruby_binary_impl, - attrs = _ATTRS, + implementation = ruby_binary_impl, + attrs = RUBY_ATTRS, test = True, toolchains = [TOOLCHAIN_TYPE_NAME], ) diff --git a/ruby/private/binary_wrapper.tpl b/ruby/private/binary_wrapper.tpl index bfedfff..6adb2a0 100644 --- a/ruby/private/binary_wrapper.tpl +++ b/ruby/private/binary_wrapper.tpl @@ -20,6 +20,14 @@ require 'rbconfig' +# Ruby 2.4 and older does not have +.children+ +# So we define it. +unless Dir.respond_to?(:children) + Dir.define_method :children do |dir| + Dir.entries(dir).reject { |entry| %w(. ..).include?(entry) } + end +end + def find_runfiles stub_filename = File.absolute_path($0) runfiles = "#{stub_filename}.runfiles" @@ -91,6 +99,13 @@ def find_ruby_binary ) end +def find_gem_binary + File.join( + RbConfig::CONFIG['bindir'], + 'gem', + ) +end + def main(args) custom_loadpaths = {loadpaths} runfiles = find_runfiles @@ -98,11 +113,14 @@ def main(args) loadpaths = create_loadpath_entries(custom_loadpaths, runfiles) loadpaths += get_repository_imports(runfiles) loadpaths += ENV['RUBYLIB'].split(':') if ENV.key?('RUBYLIB') - ENV['RUBYLIB'] = loadpaths.join(':') + ENV['RUBYLIB'] = loadpaths.sort.uniq.join(':') runfiles_envkey, runfiles_envvalue = runfiles_envvar(runfiles) ENV[runfiles_envkey] = runfiles_envvalue if runfiles_envkey + ENV["GEM_PATH"] = File.join(runfiles, "{gem_path}") if "{gem_path}" + ENV["GEM_HOME"] = File.join(runfiles, "{gem_path}") if "{gem_path}" + ruby_program = find_ruby_binary main = {main} @@ -117,6 +135,17 @@ def main(args) end end end + + # This is a jank hack because some of our gems are having issues with how + # they are being installed. Most gems are fine, but this fixes the ones that + # aren't. Put it here instead of in the library because we want to fix the + # underlying issue and then tear this out. + if {should_gem_pristine} then + gem_program = find_gem_binary + puts "Running pristine on {gems_to_pristine}" + system(gem_program + " pristine {gems_to_pristine}") + end + exec(ruby_program, *rubyopt, main, *args) # TODO(yugui) Support windows end diff --git a/ruby/private/bundle.bzl b/ruby/private/bundle.bzl deleted file mode 100644 index 636fb6b..0000000 --- a/ruby/private/bundle.bzl +++ /dev/null @@ -1,6 +0,0 @@ -load( - "//ruby/private/bundle:bundle.bzl", - _bundle_install = "bundle_install", -) - -bundle_install = _bundle_install diff --git a/ruby/private/bundle/bundle.bzl b/ruby/private/bundle/bundle.bzl deleted file mode 100644 index ad2da0b..0000000 --- a/ruby/private/bundle/bundle.bzl +++ /dev/null @@ -1,123 +0,0 @@ -load("//ruby/private:constants.bzl", "RULES_RUBY_WORKSPACE_NAME") - -def install_bundler(ctx, interpreter, install_bundler, dest, version): - args = [interpreter, install_bundler, version, dest] - environment = {"RUBYOPT": "--disable-gems"} - - result = ctx.execute(args, environment = environment) - if result.return_code: - message = "Failed to evaluate ruby snippet with {}: {}".format( - interpreter, - result.stderr, - ) - fail(message) - -def bundle_install_impl(ctx): - ctx.symlink(ctx.attr.gemfile, "Gemfile") - ctx.symlink(ctx.attr.gemfile_lock, "Gemfile.lock") - ctx.symlink(ctx.attr._create_bundle_build_file, "create_bundle_build_file.rb") - ctx.symlink(ctx.attr._install_bundler, "install_bundler.rb") - - # TODO(kig) Make Gemspec reference from Gemfile actually work - if ctx.attr.gemspec: - ctx.symlink(ctx.attr.gemspec, ctx.path(ctx.attr.gemspec).basename) - - ruby = ctx.attr.ruby_interpreter - interpreter_path = ctx.path(ruby) - - install_bundler( - ctx, - interpreter_path, - "install_bundler.rb", - "bundler", - ctx.attr.version, - ) - - bundler = Label("//:bundler/exe/bundler") - - # Install the Gems into the workspace - args = [ - ctx.path(ruby), # ruby - "--disable-gems", # prevent the addition of gem installation directories to the default load path - "-I", # Used to tell Ruby where to load the library scripts - "bundler/lib", - "bundler/exe/bundler", # run - "install", # > bundle install - "--deployment", # In the deployment mode, gems are dumped to --path and frozen; also .bundle/config file is created - "--standalone", # Makes a bundle that can work without depending on Rubygems or Bundler at runtime. - "--frozen", # Do not allow the Gemfile.lock to be updated after this install. - "--binstubs=bin", # Creates a directory and place any executables from the gem there. - "--path=lib", # The location to install the specified gems to. - ] - result = ctx.execute(args, quiet = False) - - if result.return_code: - fail("Failed to install gems: %s%s" % (result.stdout, result.stderr)) - - # Create the BUILD file to expose the gems to the WORKSPACE - args = [ - ctx.path(ruby), # ruby interpreter - "--disable-gems", # prevent the addition of gem installation directories to the default load path - "-I", # -I lib (adds this folder to $LOAD_PATH where ruby searchesf for things) - "bundler/lib", - "create_bundle_build_file.rb", # 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 - ctx.name, # Name of the target - repr(ctx.attr.excludes), - RULES_RUBY_WORKSPACE_NAME, - ] - result = ctx.execute(args, quiet = False) - - if result.return_code: - fail("Failed to create build file: %s%s" % (result.stdout, result.stderr)) - - ctx.template( - "BUILD.bazel", - ctx.attr._buildfile_template, - substitutions = { - "{repo_name}": ctx.name, - "{workspace_name}": RULES_RUBY_WORKSPACE_NAME, - }, - ) - -bundle_install = repository_rule( - implementation = bundle_install_impl, - attrs = { - "ruby_sdk": attr.string( - default = "@org_ruby_lang_ruby_toolchain", - ), - "ruby_interpreter": attr.label( - default = "@org_ruby_lang_ruby_toolchain//:ruby", - ), - "gemfile": attr.label( - allow_single_file = True, - mandatory = True, - ), - "gemfile_lock": attr.label( - allow_single_file = True, - ), - "version": attr.string( - default = "2.0.2", - ), - "gemspec": attr.label( - allow_single_file = True, - ), - "excludes": attr.string_list_dict( - doc = "List of glob patterns per gem to be excluded from the library", - ), - "_install_bundler": attr.label( - default = "%s//ruby/private/bundle:install_bundler.rb" % ( - RULES_RUBY_WORKSPACE_NAME - ), - allow_single_file = True, - ), - "_create_bundle_build_file": attr.label( - default = "%s//ruby/private/bundle:create_bundle_build_file.rb" % ( - RULES_RUBY_WORKSPACE_NAME - ), - doc = "Creates the BUILD file", - allow_single_file = True, - ), - }, -) diff --git a/ruby/private/bundle/create_bundle_build_file.rb b/ruby/private/bundle/create_bundle_build_file.rb old mode 100644 new mode 100755 index 4470418..b283b40 --- a/ruby/private/bundle/create_bundle_build_file.rb +++ b/ruby/private/bundle/create_bundle_build_file.rb @@ -1,102 +1,295 @@ +#!/usr/bin/env ruby # frozen_string_literal: true -TEMPLATE = 'load( - "{workspace_name}//ruby:defs.bzl", - "ruby_library", -) - -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "binstubs", - srcs = glob(["bin/**/*"]), - data = [":libs"], -) - -ruby_library( - name = "bundler_setup", - srcs = ["lib/bundler/setup.rb"], - visibility = ["//visibility:private"], -) - -ruby_library( - name = "bundler", - srcs = glob( - include = [ - "bundler/**/*", - ], - ), - rubyopt = ["-r${RUNFILES_DIR}/{repo_name}/lib/bundler/setup.rb"], -) - -# PULL EACH GEM INDIVIDUALLY -' - -GEM_TEMPLATE = ' -ruby_library( - name = "{name}", - srcs = glob( - include = [ - "lib/ruby/{ruby_version}/gems/{name}-{version}/**/*", - ], - exclude = {exclude}, - ), - deps = {deps}, - rubyopt = ["-r${RUNFILES_DIR}/{repo_name}/lib/bundler/setup.rb"], -) -' - -require "bundler" +BUILD_HEADER = <<~MAIN_TEMPLATE + load( + "{workspace_name}//ruby:defs.bzl", + "ruby_library", + ) + + package(default_visibility = ["//visibility:public"]) + + ruby_library( + name = "bundler_setup", + srcs = ["lib/bundler/setup.rb"], + visibility = ["//visibility:private"], + ) + + ruby_library( + name = "bundler", + srcs = glob( + include = [ + "bundler/**/*", + ], + ), + ) + + # PULL EACH GEM INDIVIDUALLY +MAIN_TEMPLATE + +GEM_TEMPLATE = <<~GEM_TEMPLATE + ruby_library( + name = "{name}", + srcs = glob( + include = [ + ".bundle/config", + "{gem_lib_files}", + "lib/ruby/{ruby_version}/specifications/{name}-{version}.gemspec", + {gem_binaries} + ], + exclude = {exclude}, + ), + deps = {deps}, + includes = ["lib/ruby/{ruby_version}/gems/{name}-{version}/lib"], + ) +GEM_TEMPLATE + +ALL_GEMS = <<~ALL_GEMS + ruby_library( + name = "gems", + srcs = glob([{bundle_lib_files}]) + glob(["bin/*"]), + includes = {bundle_lib_paths}, + ) + + ruby_library( + name = "bin", + srcs = glob(["bin/*"]), + deps = {bundle_with_binaries} + ) +ALL_GEMS + +GEM_PATH = ->(ruby_version, gem_name, gem_version) do + "lib/ruby/#{ruby_version}/gems/#{gem_name}-#{gem_version}" +end + +require 'bundler' require 'json' +require 'stringio' +require 'fileutils' +require 'tempfile' + +# colorization +class String + def colorize(color_code) + "\e[#{color_code}m#{self}\e[0m" + end + + # @formatter:off + def red; colorize(31); end + + def green; colorize(32); end + + def yellow; colorize(33); end + + def blue; colorize(34); end + + def pink; colorize(35); end + + def light_blue; colorize(36); end + + def orange; colorize(52); end + # @formatter:on +end + +class Buildifier + attr_reader :build_file, :output_file + + # @formatter:off + class BuildifierError < StandardError; end + class BuildifierNotFoundError < BuildifierError; end + class BuildifierFailedError < BuildifierError; end + class BuildifierNoBuildFileError < BuildifierError; end + # @formatter:on + + def initialize(build_file) + @build_file = build_file + + # For capturing buildifier output + @output_file = ::Tempfile.new("/tmp/#{File.dirname(File.absolute_path(build_file))}/#{build_file}.stdout").path + end + + def buildify! + raise BuildifierNoBuildFileError, 'Can\'t find the BUILD file' unless File.exist?(build_file) + + # see if we can find buildifier on the filesystem + buildifier = `bash -c 'command -v buildifier'`.strip + + raise BuildifierNotFoundError, 'Can\'t find buildifier' unless buildifier && File.executable?(buildifier) + + command = "#{buildifier} -v #{File.absolute_path(build_file)}" + system("/usr/bin/env bash -c '#{command} 1>#{output_file} 2>&1'") + code = $? + + return unless File.exist?(output_file) + + output = File.read(output_file).strip.gsub(Dir.pwd, '.').yellow + begin + FileUtils.rm_f(output_file) + rescue StandardError + nil + end + + if code == 0 + puts 'Buildifier gave 👍 '.green + (output ? " and said: #{output}" : '') + else + raise BuildifierFailedError, + 'Generated BUILD file failed buildifier, with error — '.red + "\n\n" + + output.yellow + end + end +end -def create_bundle_build_file(build_out_file, lock_file, repo_name, excludes, workspace_name) - # TODO: properly calculate path/ruby version here - # ruby_version = RUBY_VERSION # doesnt work because verion is 2.5.5 path is 2.5.0 - ruby_version = "*" - - template_out = TEMPLATE.gsub("{workspace_name}", workspace_name) - .gsub("{repo_name}", repo_name) - .gsub("{ruby_version}", ruby_version) - - # Append to the end specific gem libraries and dependencies - bundle = Bundler::LockfileParser.new(Bundler.read_file(lock_file)) - - bundle.specs.each { |spec| - deps = spec.dependencies.map(&:name) - deps += [":bundler_setup"] - - exclude_array = excludes[spec.name] || [] - # We want to exclude files and folder with spaces in them - exclude_array += ["**/* *.*", "**/* */*"] - - template_out += GEM_TEMPLATE.gsub("{exclude}", exclude_array.to_s) - .gsub("{name}", spec.name) - .gsub("{version}", spec.version.to_s) - .gsub("{deps}", deps.to_s) - .gsub("{repo_name}", repo_name) - .gsub("{ruby_version}", ruby_version) - } - - # Write the actual BUILD file - ::File.open(build_out_file, 'w') { |f| - f.puts template_out - } +class BundleBuildFileGenerator + attr_reader :workspace_name, + :repo_name, + :build_file, + :gemfile_lock, + :excludes, + :ruby_version + + DEFAULT_EXCLUDES = ['**/* *.*', '**/* */*'].freeze + + EXCLUDED_EXECUTABLES = %w(console setup).freeze + + def initialize(workspace_name:, + repo_name:, + build_file: 'BUILD.bazel', + gemfile_lock: 'Gemfile.lock', + excludes: nil) + @workspace_name = workspace_name + @repo_name = repo_name + @build_file = build_file + @gemfile_lock = gemfile_lock + @excludes = excludes + # This attribute returns 0 as the third minor version number, which happens to be + # what Ruby uses in the PATH to gems, eg. ruby 2.6.5 would have a folder called + # ruby/2.6.0/gems for all minor versions of 2.6.* + @ruby_version ||= (RUBY_VERSION.split('.')[0..1] << 0).join('.') + end + + def generate! + # when we append to a string many times, using StringIO is more efficient. + template_out = StringIO.new + template_out.puts BUILD_HEADER + .gsub('{workspace_name}', workspace_name) + .gsub('{repo_name}', repo_name) + .gsub('{ruby_version}', ruby_version) + .gsub('{bundler_setup}', bundler_setup_require) + + # strip bundler version so we can process this file + remove_bundler_version! + + # Append to the end specific gem libraries and dependencies + bundle = Bundler::LockfileParser.new(Bundler.read_file(gemfile_lock)) + bundle_lib_paths = [] + bundle_binaries = {} # gem-name => [ gem's binaries ], ... + gems = bundle.specs.map(&:name) + + bundle.specs.each { |spec| register_gem(spec, template_out, bundle_lib_paths, bundle_binaries) } + + template_out.puts ALL_GEMS + .gsub('{bundle_lib_files}', to_flat_string(bundle_lib_paths.map { |p| "#{p}/**/*" })) + .gsub('{bundle_with_binaries}', bundle_binaries.keys.map { |g| ":#{g}" }.to_s) + .gsub('{bundle_binaries}', bundle_binaries.values.flatten.to_s) + .gsub('{bundle_lib_paths}', bundle_lib_paths.to_s) + .gsub('{bundler_setup}', bundler_setup_require) + .gsub('{bundle_deps}', gems.map { |g| ":#{g}" }.to_s) + .gsub('{exclude}', DEFAULT_EXCLUDES.to_s) + + ::File.open(build_file, 'w') { |f| f.puts template_out.string } + end + + private + + def bundler_setup_require + @bundler_setup_require ||= "-r#{runfiles_path('lib/bundler/setup.rb')}" + end + + def runfiles_path(path) + "${RUNFILES_DIR}/#{repo_name}/#{path}" + end + + # This method scans the contents of the Gemfile.lock and if it finds BUNDLED WITH + # it strips that line + the line below it, so that any version of bundler would work. + def remove_bundler_version! + contents = File.read(gemfile_lock) + return unless contents =~ /BUNDLED WITH/ + + temp_gemfile_lock = "#{gemfile_lock}.no-bundle-version" + system %(sed -n '/BUNDLED WITH/q;p' "#{gemfile_lock}" > #{temp_gemfile_lock}) + ::FileUtils.rm_f(gemfile_lock) if File.symlink?(gemfile_lock) # it's just a symlink + ::FileUtils.move(temp_gemfile_lock, gemfile_lock, force: true) + end + + def register_gem(spec, template_out, bundle_lib_paths, bundle_binaries) + gem_path = GEM_PATH[ruby_version, spec.name, spec.version] + bundle_lib_paths << gem_lib_path = gem_path + '/lib' + + # paths to search for executables + gem_binaries = find_bundle_binaries(gem_path) + bundle_binaries[spec.name] = gem_binaries unless gem_binaries.nil? || gem_binaries.empty? + + deps = spec.dependencies.map { |d| ":#{d.name}" } + + warn("registering gem #{spec.name} with binaries: #{gem_binaries}") if bundle_binaries.key?(spec.name) + + template_out.puts GEM_TEMPLATE + .gsub('{gem_lib_path}', gem_lib_path) + .gsub('{gem_lib_files}', gem_lib_path + '/**/*') + .gsub('{gem_binaries}', to_flat_string(gem_binaries)) + .gsub('{exclude}', exclude_array(spec.name).to_s) + .gsub('{name}', spec.name) + .gsub('{version}', spec.version.to_s) + .gsub('{deps}', deps.to_s) + .gsub('{repo_name}', repo_name) + .gsub('{ruby_version}', ruby_version) + .gsub('{bundler_setup}', bundler_setup_require) + end + + def find_bundle_binaries(gem_path) + gem_bin_paths = %W(#{gem_path}/bin #{gem_path}/exe) + + gem_bin_paths + .map do |bin_path| + Dir # grab all files under bin/ and exe/ inside the gem folder + .glob("#{bin_path}/*") # convert to File object + .map { |b| f = File.new(b); File.executable?(f) ? f : nil } + .compact # remove non-executables, take basename, minus binary defaults + .map { |f| File.basename(f.path) } - EXCLUDED_EXECUTABLES # that bundler installs with bundle gem e + warn("ERROR running buildifier on the generated build file [#{build_file}] ➔ #{e.message.orange}") + end end diff --git a/ruby/private/bundle/def.bzl b/ruby/private/bundle/def.bzl new file mode 100644 index 0000000..d3fc880 --- /dev/null +++ b/ruby/private/bundle/def.bzl @@ -0,0 +1,185 @@ +load( + "//ruby/private:constants.bzl", + "BUNDLE_ATTRS", + "BUNDLE_BINARY", + "BUNDLE_BIN_PATH", + "BUNDLE_PATH", + "RULES_RUBY_WORKSPACE_NAME", + "SCRIPT_BUILD_FILE_GENERATOR", + "SCRIPT_INSTALL_GEM", +) +load("//ruby/private:providers.bzl", "RubyRuntimeContext") + +# Runs bundler with arbitrary arguments +# eg: run_bundler(runtime_ctx, [ "lock", " --gemfile", "Gemfile.rails5" ]) +def run_bundler(runtime_ctx, bundler_arguments, previous_result): + # Now we are running bundle install + bundler_command = bundler_arguments[0] + bundler_args = [] + + # add --verbose to all commands except install + if bundler_command != "install": + bundler_args.append("--verbose") + + bundler_args += bundler_arguments[1:] + + args = [ + runtime_ctx.interpreter, # ruby + "-I", # Used to tell Ruby where to load the library scripts + BUNDLE_PATH, # Add vendor/bundle to the list of resolvers + BUNDLE_BINARY, # our binary + ] + [bundler_command] + bundler_args + + # print("Bundler Command:\n\n", args) + + return runtime_ctx.ctx.execute( + args, + quiet = False, + environment = runtime_ctx.environment, + ) + +# +# Sets local bundler config values by calling +# +# $ bundle config --local | --global config-option config-value +# +# @config_category can be either 'local' or 'global' +def set_bundler_config(runtime_ctx, previous_result, config_category = "local"): + # Bundler is deprecating various flags in favor of the configuration. + # HOWEVER — for reasons I can't explain, Bazel runs "bundle install" *prior* + # to setting these flags. So the flags are then useless until we can force the + # order and ensure that Bazel first downloads Bundler, then sets config, then + # runs bundle install. Until then, it's a wild west out here. + # + # Set local configuration options for bundler + bundler_config = { + "deployment": "true", + "standalone": "true", + "force": "false", + "redownload": "false", + "frozen": "true", + "path": BUNDLE_PATH, + "jobs": "20", + "shebang": runtime_ctx.interpreter, + } + + last_result = previous_result + + for option, value in bundler_config.items(): + args = ["config", "set", "--%s" % (config_category), option, value] + result = run_bundler(runtime_ctx, args, last_result) + last_result = result + if result.return_code: + message = "Failed to set bundle config {} to {}: {}".format( + option, + value, + result.stderr, + ) + fail(message) + + return last_result + +# This function is called "pure_ruby" because it downloads and unpacks the gem +# file into a given folder, which for gems without C-extensions is the same +# as install. To support gems that have C-extensions, the Ruby file install_gem.rb +# will need to be modified to use Gem::Installer.at(path).install(gem) API. +def install_pure_ruby_gem(runtime_ctx, gem_name, gem_version, folder): + # USAGE: ./install_bundler.rb gem-name gem-version destination-folder + args = [ + runtime_ctx.interpreter, + SCRIPT_INSTALL_GEM, + gem_name, + gem_version, + folder, + ] + result = runtime_ctx.ctx.execute(args, environment = runtime_ctx.environment) + if result.return_code: + message = "Failed to install gem {}-{} to {} with {}: {}".format( + gem_name, + gem_version, + folder, + runtime_ctx.interpreter, + result.stderr, + ) + fail(message) + else: + return result + +def install_bundler(runtime_ctx, bundler_version): + return install_pure_ruby_gem( + runtime_ctx, + "bundler", + bundler_version, + "bundler", + ) + +def bundle_install(runtime_ctx, previous_result): + result = run_bundler( + runtime_ctx, + [ + "install", + "--binstubs={}".format(BUNDLE_BIN_PATH), + "--path={}".format(BUNDLE_PATH), + "--deployment", + "--standalone", + "--frozen", + ], + previous_result, + ) + + if result.return_code: + fail("bundle install failed: %s%s" % (result.stdout, result.stderr)) + else: + return result + +def generate_bundle_build_file(runtime_ctx, previous_result): + # 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 = [ + runtime_ctx.interpreter, # ruby interpreter + "--enable=gems", # prevent the addition of gem installation directories to the default load path + "-I", # -I lib (adds this folder to $LOAD_PATH where ruby searches for things) + "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 + runtime_ctx.ctx.name, # Name of the target + repr(runtime_ctx.ctx.attr.excludes), + RULES_RUBY_WORKSPACE_NAME, + ] + + result = runtime_ctx.ctx.execute(args, quiet = False) + if result.return_code: + 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._create_bundle_build_file, SCRIPT_BUILD_FILE_GENERATOR) + ctx.symlink(ctx.attr._install_bundler, SCRIPT_INSTALL_GEM) + + bundler_version = ctx.attr.bundler_version + + # Setup this provider that we pass around between functions for convenience + runtime_ctx = RubyRuntimeContext( + ctx = ctx, + interpreter = ctx.path(ctx.attr.ruby_interpreter), + environment = {"RUBYOPT": "--enable-gems"}, + ) + + # 1. Install the right version of the Bundler Gem + result = install_bundler(runtime_ctx, bundler_version) + + # 2. Set Bundler config in the .bundle/config file + result = set_bundler_config(runtime_ctx, result) + + # 3. Run bundle install + result = bundle_install(runtime_ctx, result) + + # 4. Generate the BUILD file for the bundle + generate_bundle_build_file(runtime_ctx, result) + +ruby_bundle_install = repository_rule( + implementation = _ruby_bundle_impl, + attrs = BUNDLE_ATTRS, +) diff --git a/ruby/private/bundle/install_bundler.rb b/ruby/private/bundle/download_gem.rb old mode 100644 new mode 100755 similarity index 61% rename from ruby/private/bundle/install_bundler.rb rename to ruby/private/bundle/download_gem.rb index ff4a3d9..780c968 --- a/ruby/private/bundle/install_bundler.rb +++ b/ruby/private/bundle/download_gem.rb @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby # frozen_string_literal: true require 'rubygems' @@ -11,17 +12,21 @@ def unpack_gem(name, version, dest = Dir.pwd) source = Gem::Source.new('https://rubygems.org') spec = source.fetch_spec Gem::NameTuple.new(name, version) - Dir.mktmpdir { |dir| + Dir.mktmpdir do |dir| Dir.chdir(dir) { source.download(spec) } downloaded = File.join(dir, "#{name}-#{version}.gem") - Gem::Package.new(downloaded).extract_files dest - } + Gem::Package.new(downloaded).extract_files(dest) + end end def main - version = ARGV[0] - dir = ARGV[1] || Dir.pwd - unpack_gem('bundler', version, dir) + gem_name, gem_version, dir, = *ARGV + dir ||= Dir.pwd + unless gem_name && gem_version + puts "USAGE: #{$0} gem-name gem-version destination-folder" + exit 1 + end + unpack_gem(gem_name, gem_version, dir) end if $0 == __FILE__ diff --git a/ruby/private/constants.bzl b/ruby/private/constants.bzl index 0363c2f..6600fc7 100644 --- a/ruby/private/constants.bzl +++ b/ruby/private/constants.bzl @@ -1,2 +1,146 @@ -RULES_RUBY_WORKSPACE_NAME = "@bazelruby_ruby_rules" +load(":providers.bzl", "RubyLibrary") + +RULES_RUBY_WORKSPACE_NAME = "@bazelruby_rules_ruby" TOOLCHAIN_TYPE_NAME = "%s//ruby:toolchain_type" % RULES_RUBY_WORKSPACE_NAME + +DEFAULT_BUNDLER_VERSION = "2.1.2" +DEFAULT_RSPEC_ARGS = {"--format": "documentation", "--force-color": None} +DEFAULT_RSPEC_GEMS = ["rspec", "rspec-its"] +DEFAULT_BUNDLE_NAME = "@bundle//" + +BUNDLE_BIN_PATH = "bin" +BUNDLE_PATH = "lib" +BUNDLE_BINARY = "bundler/exe/bundler" + +SCRIPT_INSTALL_GEM = "download_gem.rb" +SCRIPT_BUILD_FILE_GENERATOR = "create_bundle_build_file.rb" + +RUBY_ATTRS = { + "srcs": attr.label_list( + allow_files = True, + ), + "deps": attr.label_list( + providers = [RubyLibrary], + ), + "includes": attr.string_list(), + "rubyopt": attr.string_list(), + "data": attr.label_list( + allow_files = True, + ), + "main": attr.label( + allow_single_file = True, + ), + "force_gem_pristine": attr.string_list( + doc = "Jank hack. Run gem pristine on some gems that don't handle symlinks well", + ), + "_wrapper_template": attr.label( + allow_single_file = True, + default = "binary_wrapper.tpl", + ), + "_misc_deps": attr.label_list( + allow_files = True, + default = ["@bazel_tools//tools/bash/runfiles"], + ), +} + +_RSPEC_ATTRS = { + "bundle": attr.string( + default = DEFAULT_BUNDLE_NAME, + doc = "Name of the bundle where the rspec gem can be found, eg @bundle//", + ), + "rspec_args": attr.string_list( + default = [], + doc = "Arguments passed to rspec executable", + ), + "rspec_executable": attr.label( + default = "%s:bin/rspec" % (DEFAULT_BUNDLE_NAME), + allow_single_file = True, + doc = "RSpec Executable Label", + ), +} + +RSPEC_ATTRS = {} + +RSPEC_ATTRS.update(RUBY_ATTRS) +RSPEC_ATTRS.update(_RSPEC_ATTRS) + +BUNDLE_ATTRS = { + "ruby_sdk": attr.string( + default = "@org_ruby_lang_ruby_toolchain", + ), + "ruby_interpreter": attr.label( + default = "@org_ruby_lang_ruby_toolchain//:ruby", + ), + "gemfile": attr.label( + allow_single_file = True, + mandatory = True, + ), + "gemfile_lock": attr.label( + allow_single_file = True, + ), + "bundler_version": attr.string( + default = DEFAULT_BUNDLER_VERSION, + ), + "excludes": attr.string_list_dict( + doc = "List of glob patterns per gem to be excluded from the library", + ), + "_install_bundler": attr.label( + default = "%s//ruby/private/bundle:%s" % ( + RULES_RUBY_WORKSPACE_NAME, + SCRIPT_INSTALL_GEM, + ), + allow_single_file = True, + ), + "_create_bundle_build_file": attr.label( + default = "%s//ruby/private/bundle:%s" % ( + RULES_RUBY_WORKSPACE_NAME, + SCRIPT_BUILD_FILE_GENERATOR, + ), + doc = "Creates the BUILD file", + allow_single_file = True, + ), +} + +GEMSPEC_ATTRS = { + "gem_name": attr.string(), + "gem_version": attr.string(default = "0.0.1"), + "gem_summary": attr.string(), + "gem_description": attr.string(), + "gem_homepage": attr.string(), + "gem_authors": attr.string_list(), + "gem_author_emails": attr.string_list(), + "gem_runtime_dependencies": attr.string_dict( + allow_empty = True, + doc = "Key value pairs of gem dependencies (name, version) where version can be None", + ), + "gem_development_dependencies": attr.string_dict( + allow_empty = True, + default = { + "rspec": "", + "rspec-its": "", + "rubocop": "", + }, + doc = "Key value pairs of gem dependencies (name, version) where version can be None", + ), + "srcs": attr.label_list( + allow_files = True, + default = [], + ), + "require_paths": attr.string_list( + default = ["lib"], + ), + "deps": attr.label_list( + allow_files = True, + ), + "data": attr.label_list( + allow_files = True, + ), + "_gemspec_template": attr.label( + allow_single_file = True, + default = "%s//ruby/private/gemspec:gemspec_template.tpl" % RULES_RUBY_WORKSPACE_NAME, + ), + "_readme_template": attr.label( + allow_single_file = True, + default = "%s//ruby/private/gemspec:readme_template.tpl" % RULES_RUBY_WORKSPACE_NAME, + ), +} diff --git a/ruby/private/dependencies.bzl b/ruby/private/dependencies.bzl index 397a6eb..883bcf2 100644 --- a/ruby/private/dependencies.bzl +++ b/ruby/private/dependencies.bzl @@ -1,6 +1,6 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -def ruby_rules_dependencies(): +def rules_ruby_dependencies(): if "bazel_skylib" not in native.existing_rules(): http_archive( name = "bazel_skylib", @@ -10,3 +10,13 @@ def ruby_rules_dependencies(): ], sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", ) + + if "rules_pkg" not in native.existing_rules(): + # Use Grahams improved rules_zip version until google merges it into mainline. + # https://github.com/bazelbuild/rules_pkg/pull/127 + http_archive( + name = "rules_pkg", + url = "https://github.com/grahamjenson/rules_pkg/archive/3e0cd514ad1cdd2d23ab3d427d34436f75060018.zip", + sha256 = "85e26971904cbb387688bd2a9e87c105f7cd7d986dc1b96bb1391924479c5ef6", + strip_prefix = "rules_pkg-3e0cd514ad1cdd2d23ab3d427d34436f75060018/pkg", + ) diff --git a/ruby/private/gemspec/BUILD.bazel b/ruby/private/gemspec/BUILD.bazel new file mode 100644 index 0000000..64bbe87 --- /dev/null +++ b/ruby/private/gemspec/BUILD.bazel @@ -0,0 +1,9 @@ +package(default_visibility = ["//ruby/private/gemspec:__pkg__"]) + +exports_files( + [ + "gemspec_template.tpl", + "readme_template.tpl", + ], + visibility = ["//visibility:public"], +) diff --git a/ruby/private/gemspec/def.bzl b/ruby/private/gemspec/def.bzl new file mode 100644 index 0000000..9560da8 --- /dev/null +++ b/ruby/private/gemspec/def.bzl @@ -0,0 +1,176 @@ +load( + "//ruby/private:providers.bzl", + "RubyGem", + "RubyLibrary", +) +load( + "//ruby/private:constants.bzl", + "GEMSPEC_ATTRS", +) +load( + "@rules_pkg//:pkg.bzl", + "pkg_zip", +) + +def _get_transitive_srcs(srcs, deps): + for dep in deps: + print(dep[RubyLibrary].transitive_ruby_srcs) + + return depset( + srcs, + transitive = [dep[RubyLibrary].transitive_ruby_srcs for dep in deps], + ) + +def _unique_elems(list): + _out = [] + _prev = None + for elem in sorted(list): + if _prev != elem: + _out.append(elem) + + return _out + +# Converts gem name and optionally a version into a +# gemspec line "spec.add_[development_]dependency 'gem-name', [ 'gem-version' ]" +def _gem_dependency(name, version = "", development = False): + dependency_type = "spec.add_development_dependency" if development else "spec.add_runtime_dependency" + + output = "%s '%s'" % (dependency_type, name) + if version != "": + output += ", '%s'" % version + + return output + +# Converts gem name and optionally to a bullet list +def _markdown_gem_dependency(name, version = ""): + output = " * %s " % name + if version != "": + output += " (version %s) " % (version) + + return output + +def _markdown_ul(list = []): + return ("\n * " + "\n * ".join(list) + "\n") + +# Converts a dictionary (key = gem name, value = gem version or None) +# to a string to be inserted into the gemspec. +def _gem_runtime_dependencies(gem_dict = {}): + dependencies = [_gem_dependency(k, v) for k, v in gem_dict.items()] + return ("\n " + "\n ".join(dependencies)) + +# Converts a dictionary (key = gem name, value = gem version or None) +# to a string to be inserted into the gemspec. +def _markdown_gem_runtime_dependencies(gem_dict = {}, type = "Runtime"): + dependencies = [_markdown_gem_dependency(k, v) for k, v in gem_dict.items()] + output = "\n### %s Dependencies\n\n" % type + output += "\n " + "\n ".join(dependencies) + "\n\n" + return (output) + +def _gem_impl(ctx): + gemspec = ctx.actions.declare_file("%s.gemspec" % ctx.attr.gem_name) + gem_readme = ctx.actions.declare_file("README.md") + + _ruby_files = [] + _require_paths = [] + + for file in _get_transitive_srcs([], ctx.attr.deps).to_list(): + _ruby_files.append(file.short_path) + _require_paths.append(file.dirname) + + if len(_ruby_files) == 0: + _gem_sources = "`git ls-files -z`.split(\"\\x0\").reject { |f| f.match(/^(test|spec|features)\\//) }" + else: + _gem_sources = repr(_ruby_files) + + if ctx.attr.gem_homepage != "": + _gem_title = "[%s](%s)" % (ctx.attr.gem_name, ctx.attr.gem_homepage) + else: + _gem_title = "%s" % (ctx.attr.gem_name) + + ctx.actions.expand_template( + template = ctx.file._gemspec_template, + output = gemspec, + substitutions = { + "{gem_author_emails}": repr(ctx.attr.gem_author_emails), + "{gem_authors}": repr(ctx.attr.gem_authors), + "{gem_runtime_dependencies}": _gem_runtime_dependencies(ctx.attr.gem_runtime_dependencies), + "{gem_description}": ctx.attr.gem_description if ctx.attr.gem_description else ctx.attr.gem_summary, + "{gem_development_dependencies}": _gem_runtime_dependencies(ctx.attr.gem_development_dependencies), + "{gem_homepage}": ctx.attr.gem_homepage, + "{gem_name}": ctx.attr.gem_name, + "{gem_require_paths}": repr(["lib"]), + "{gem_sources}": _gem_sources, + "{gem_summary}": ctx.attr.gem_summary, + "{gem_version}": ctx.attr.gem_version, + }, + ) + + _dependencies = _markdown_gem_runtime_dependencies(ctx.attr.gem_runtime_dependencies, "Runtime") + _dependencies += _markdown_gem_runtime_dependencies(ctx.attr.gem_development_dependencies, "Development") + + ctx.actions.expand_template( + template = ctx.file._readme_template, + output = gem_readme, + substitutions = { + "{gem_authorship}": _markdown_ul(ctx.attr.gem_authors), + "{gem_runtime_dependencies}": _dependencies, + "{gem_description}": ctx.attr.gem_description if ctx.attr.gem_description else ctx.attr.gem_summary, + "{gem_name}": ctx.attr.gem_name, + "{gem_summary}": ctx.attr.gem_summary, + "{gem_title}": _gem_title, + "{gem_version}": ctx.attr.gem_version, + }, + ) + + return [ + DefaultInfo( + files = _get_transitive_srcs([gemspec, gem_readme], ctx.attr.deps), + ), + RubyGem( + ctx = ctx, + gem_author_emails = ctx.attr.gem_author_emails, + gem_authors = ctx.attr.gem_authors, + gem_runtime_dependencies = ctx.attr.gem_runtime_dependencies, + gem_description = ctx.attr.gem_description, + gem_development_dependencies = ctx.attr.gem_development_dependencies, + gem_homepage = ctx.attr.gem_homepage, + gem_name = ctx.attr.gem_name, + gem_summary = ctx.attr.gem_summary, + gem_version = ctx.attr.gem_version, + ), + ] + +gemspec = rule( + implementation = _gem_impl, + attrs = GEMSPEC_ATTRS, + provides = [DefaultInfo, RubyGem], +) + +def gem( + name, + gem_name, + gem_version, + srcs, + **kwargs): + _zip_name = "%s-%s" % (gem_name, gem_version) + _gemspec_name = name + ".gemspec" + + gemspec( + name = _gemspec_name, + gem_name = gem_name, + gem_version = gem_version, + srcs = srcs, + **kwargs + ) + + pkg_zip( + name = _zip_name, + srcs = srcs + [":" + _gemspec_name], + strip_prefix = "./", + ) + + native.alias( + name = name, + actual = ":" + _zip_name, + visibility = ["//visibility:public"], + ) diff --git a/ruby/private/gemspec/gemspec_template.tpl b/ruby/private/gemspec/gemspec_template.tpl new file mode 100644 index 0000000..7c0da02 --- /dev/null +++ b/ruby/private/gemspec/gemspec_template.tpl @@ -0,0 +1,25 @@ +# vim: ft=ruby +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |spec| + spec.name = "{gem_name}" + spec.version = "{gem_version}" + spec.summary = "{gem_summary}" + spec.description = "{gem_description}" + spec.homepage = "{gem_homepage}" + + spec.authors = {gem_authors} + spec.email = {gem_author_emails} + + spec.files = {gem_sources} + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = {gem_require_paths} + + spec.required_ruby_version = '>= 2.3' + + {gem_runtime_dependencies} + + {gem_development_dependencies} +end diff --git a/ruby/private/gemspec/readme_template.tpl b/ruby/private/gemspec/readme_template.tpl new file mode 100644 index 0000000..33b71d8 --- /dev/null +++ b/ruby/private/gemspec/readme_template.tpl @@ -0,0 +1,23 @@ +# {gem_title} + +## Version {gem_version} + +> NOTE: You are reading the auto-generated README for Gem {gem_name}. + +## Summary + +{gem_summary} + +## Description + +{gem_description} + +## Dependencies + +{gem_runtime_dependencies} + +## Author(s) + +Copyright © 2020 + +{gem_authorship} diff --git a/ruby/private/providers.bzl b/ruby/private/providers.bzl index 2101bdd..33dbe1e 100644 --- a/ruby/private/providers.bzl +++ b/ruby/private/providers.bzl @@ -5,3 +5,28 @@ RubyLibrary = provider( "rubyopt", ], ) + +RubyRuntimeContext = provider( + doc = "Carries info required to execute Ruby Scripts", + fields = [ + "ctx", + "interpreter", + "environment", + ], +) + +RubyGem = provider( + doc = "Carries info required to package a ruby gem", + fields = [ + "ctx", + "gem_author_emails", + "gem_authors", + "gem_runtime_dependencies", + "gem_description", + "gem_development_dependencies", + "gem_homepage", + "gem_name", + "gem_summary", + "gem_version", + ], +) diff --git a/ruby/private/rspec.bzl b/ruby/private/rspec.bzl new file mode 100644 index 0000000..e86603b --- /dev/null +++ b/ruby/private/rspec.bzl @@ -0,0 +1,73 @@ +load( + ":constants.bzl", + "DEFAULT_BUNDLE_NAME", + "DEFAULT_RSPEC_ARGS", + "DEFAULT_RSPEC_GEMS", + "RSPEC_ATTRS", + "TOOLCHAIN_TYPE_NAME", +) +load(":binary.bzl", "ruby_binary_macro") + +def ruby_rspec( + name, + srcs, + specs, + deps = None, + size = "small", + rspec_args = None, # This is expected to be a dictionary + bundle = DEFAULT_BUNDLE_NAME, + visibility = None, + **kwargs): + if specs == None: + specs = [] + + if srcs == None: + srcs = [] + + if rspec_args == None: + rspec_args = {} + + args_list = [] + + args_dict = {} + args_dict.update(DEFAULT_RSPEC_ARGS) + args_dict.update(rspec_args) + + # We pass the respec_args as a dictionary so that you can overwrite + # the default rspec arguments with custom ones. + for option, value in [(option, value) for option, value in args_dict.items()]: + if value != None: + args_list.append("%s %s" % (option, value)) + else: + args_list.append("%s" % (option)) + + args_list += specs + + rspec_gems = ["%s:%s" % (bundle, gem) for gem in DEFAULT_RSPEC_GEMS] + + deps += rspec_gems + deps += ["%s:bin" % bundle] + + ruby_rspec_test( + name = name, + visibility = visibility, + args = args_list, + srcs = srcs + specs, + deps = deps, + size = size, + **kwargs + ) + +def _ruby_rspec_test_impl(ctx): + return ruby_binary_macro( + ctx, + ctx.file.rspec_executable, + ctx.attr.srcs, + ) + +ruby_rspec_test = rule( + implementation = _ruby_rspec_test_impl, + attrs = RSPEC_ATTRS, + test = True, + toolchains = [TOOLCHAIN_TYPE_NAME], +) diff --git a/ruby/private/rubocop/BUILD.bazel b/ruby/private/rubocop/BUILD.bazel new file mode 100644 index 0000000..94ca1ed --- /dev/null +++ b/ruby/private/rubocop/BUILD.bazel @@ -0,0 +1,6 @@ +package(default_visibility = ["//ruby/private:__pkg__"]) + +exports_files( + ["runner.sh.tpl"], + visibility = ["//visibility:public"], +) diff --git a/ruby/private/rubocop/def.bzl b/ruby/private/rubocop/def.bzl new file mode 100644 index 0000000..0319906 --- /dev/null +++ b/ruby/private/rubocop/def.bzl @@ -0,0 +1,20 @@ +load("@bazelruby_rules_ruby//ruby/private:binary.bzl", "ruby_binary") + +# This wraps an rb_binary in a script that is executed from the workspace folder +def rubocop(name, bin, deps): + bin_name = name + "-ruby" + ruby_binary( + name = bin_name, + main = bin, + deps = deps, + ) + + runner = "@bazelruby_rules_ruby//ruby/private/rubocop:runner.sh.tpl" + native.genrule( + name = name, + tools = [bin_name], + srcs = [runner], + executable = True, + outs = [name + ".sh"], + cmd = "sed \"s~{{BIN}}~$(location %s)~g\" $(location %s) > \"$@\"" % (bin_name, runner), + ) diff --git a/ruby/private/rubocop/runner.sh.tpl b/ruby/private/rubocop/runner.sh.tpl new file mode 100644 index 0000000..d0f3d52 --- /dev/null +++ b/ruby/private/rubocop/runner.sh.tpl @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +( + cd $BUILD_WORKSPACE_DIRECTORY && \ + {{BIN}} $@ +) diff --git a/ruby/private/sdk.bzl b/ruby/private/sdk.bzl index 12b5046..dc6c13c 100644 --- a/ruby/private/sdk.bzl +++ b/ruby/private/sdk.bzl @@ -1,19 +1,19 @@ load( - "@bazelruby_ruby_rules//ruby/private/toolchains:ruby_runtime.bzl", + "@bazelruby_rules_ruby//ruby/private/toolchains:ruby_runtime.bzl", _ruby_runtime = "ruby_runtime", ) -def ruby_register_toolchains(version = "host"): +def rules_ruby_select_sdk(version = "host"): """Registers ruby toolchains in the WORKSPACE file.""" - supported_versions = ["host", "2.6.3", "2.6.5"] + supported_versions = ["host", "2.6.3", "2.6.5", "2.7.0"] if version in supported_versions: _ruby_runtime( name = "org_ruby_lang_ruby_toolchain", version = version, ) else: - fail("ruby_register_toolchains: unsupported ruby version '%s' not in '%s'" % (version, supported_versions)) + fail("rules_ruby_select_sdk: unsupported ruby version '%s' not in '%s'" % (version, supported_versions)) native.register_toolchains( "@org_ruby_lang_ruby_toolchain//:toolchain", diff --git a/ruby/private/toolchains/repository_context.bzl b/ruby/private/toolchains/repository_context.bzl index b82c922..b151531 100644 --- a/ruby/private/toolchains/repository_context.bzl +++ b/ruby/private/toolchains/repository_context.bzl @@ -9,7 +9,7 @@ def _eval_ruby(ruby, script, options = None): arguments.extend(options) arguments.extend(["-e", script]) - environment = {"RUBYOPT": "--disable-gems"} + environment = {"RUBYOPT": "--enable=gems"} result = ruby._ctx.execute(arguments, environment = environment) if result.return_code: diff --git a/ruby/private/toolchains/ruby_runtime.bzl b/ruby/private/toolchains/ruby_runtime.bzl index 875f6d3..7fe18c2 100644 --- a/ruby/private/toolchains/ruby_runtime.bzl +++ b/ruby/private/toolchains/ruby_runtime.bzl @@ -4,9 +4,9 @@ load("//ruby/private/toolchains:repository_context.bzl", "ruby_repository_contex def _install_ruby_version(ctx, version): print("download and extract ruby-build") ctx.download_and_extract( - url = "https://github.com/rbenv/ruby-build/archive/v20191205.tar.gz", - sha256 = "d8ffe806a215b3afacead72e766f293ce380c78a143911b84cdb5f33e20a5284", - stripPrefix = "ruby-build-20191205", + url = "https://github.com/rbenv/ruby-build/archive/v20200224.tar.gz", + sha256 = "dc3799a1c784c9a0f214a3c0c861a0bb798cd40ee2df49bce95b4c95adf6fc79", + stripPrefix = "ruby-build-20200224", ) install_path = "./build" @@ -39,7 +39,7 @@ def _relativate(path): def _list_libdirs(ruby): """List the LOAD_PATH of the ruby""" - paths = ruby.eval(ruby, 'print $:.join("\\n")') + paths = ruby.eval(ruby, "print $:.join(\"\\n\")") paths = sorted(paths.split("\n")) rel_paths = [_relativate(path) for path in paths] return (paths, rel_paths) @@ -57,7 +57,7 @@ def _install_ruby(ctx, ruby): ctx.symlink(ruby.interpreter_realpath, ruby.rel_interpreter_path) # Places the interpreter at a predictable place regardless of the actual binary name - # so that bundle_install can depend on it. + # so that ruby_bundle can depend on it. ctx.template( "ruby", ctx.attr._interpreter_wrapper_template, diff --git a/ruby/tests/BUILD.bazel b/ruby/tests/BUILD.bazel index a28e195..d26dabe 100644 --- a/ruby/tests/BUILD.bazel +++ b/ruby/tests/BUILD.bazel @@ -1,14 +1,19 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") load( "//ruby:defs.bzl", "ruby_binary", "ruby_test", ) load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") -load("@io_bazel_rules_docker//container:container.bzl", "container_image") +load( + "@io_bazel_rules_docker//container:container.bzl", + "container_image", +) # Checks if args are correctly passed to the ruby script. ruby_test( name = "args_check_ruby_test", + size = "small", srcs = ["args_check.rb"], args = [ "foo", @@ -26,6 +31,7 @@ ruby_binary( # Checks if a ruby_binary is a valid src in sh_* rules sh_test( name = "args_check_sh_test", + size = "small", srcs = ["args_check"], args = [ "foo", @@ -36,6 +42,7 @@ sh_test( ruby_test( name = "include_order_check", + size = "small", srcs = ["include_order_check.rb"], deps = [ "//ruby/tests/testdata:a", @@ -48,6 +55,7 @@ ruby_test( # Tests if :ruby_bin can run in sh_binary sh_test( name = "runtime_run_ruby_test", + size = "small", srcs = ["runtime_run_ruby_test.sh"], args = [ "$(location args_check.rb)", @@ -82,6 +90,7 @@ genrule( sh_test( name = "genrule_run_ruby_test", + size = "small", srcs = ["genrules_run_ruby_test.sh"], ) @@ -102,7 +111,7 @@ sh_test( # 6a) case 3 in the context of a genrule # # Also all of the cases above must correctly configure environment variables -# so that their subprocesses whose binaries are generated by Bazel can run with +# so that their sub-processes with binaries generated by Bazel can run with # their runfiles. ruby_binary( @@ -111,7 +120,7 @@ ruby_binary( main = "load_path_in_runfiles_test.rb", deps = [ "//ruby/tests/testdata:g", - "@bazelruby_ruby_rules_ruby_tests_testdata_another_workspace//baz/qux:j", + "@bazelruby_rules_ruby_ruby_tests_testdata_another_workspace//baz/qux:j", ], ) @@ -123,17 +132,19 @@ sh_binary( # runfiles resolution test (case 4a) ruby_test( name = "load_path_in_runfiles_test_4a", + size = "small", srcs = ["load_path_in_runfiles_test.rb"], main = "load_path_in_runfiles_test.rb", deps = [ "//ruby/tests/testdata:g", - "@bazelruby_ruby_rules_ruby_tests_testdata_another_workspace//baz/qux:j", + "@bazelruby_rules_ruby_ruby_tests_testdata_another_workspace//baz/qux:j", ], ) # runfiles resolution test (case 4b) sh_test( name = "load_path_in_runfiles_test_4b", + size = "small", srcs = ["load_path_in_runfiles_test_3.sh"], data = [":load_path_in_runfiles_sh_binary"], ) @@ -151,6 +162,7 @@ genrule( # runfiles resolution test (case 5a) sh_test( name = "load_path_in_runfiles_test_5a", + size = "small", srcs = ["load_path_in_runfiles_test_5a.sh"], ) @@ -220,6 +232,7 @@ config_setting( ruby_test( name = "ext_test", + size = "small", srcs = ["ext_test.rb"], data = select({ ":requires_bundle": ["example_ext.bundle"], @@ -237,13 +250,13 @@ pkg_tar( include_runfiles = True, package_dir = "/app", remap_paths = { - "ruby": "load_path_in_runfiles.runfiles/bazelruby_ruby_rules/ruby", + "ruby": "load_path_in_runfiles.runfiles/bazelruby_rules_ruby/ruby", ".": "load_path_in_runfiles.runfiles/", }, strip_prefix = "dummy", symlinks = { - "/app/load_path_in_runfiles.runfiles/bazelruby_ruby_rules/external": "/app/load_path_in_runfiles.runfiles", - "/app/load_path_in_runfiles": "/app/load_path_in_runfiles.runfiles/bazelruby_ruby_rules/ruby/tests/load_path_in_runfiles", + "/app/load_path_in_runfiles.runfiles/bazelruby_rules_ruby/external": "/app/load_path_in_runfiles.runfiles", + "/app/load_path_in_runfiles": "/app/load_path_in_runfiles.runfiles/bazelruby_rules_ruby/ruby/tests/load_path_in_runfiles", }, ) @@ -256,6 +269,7 @@ container_image( sh_test( name = "load_path_in_runfiles_container_test", + size = "small", srcs = ["container_test.sh"], args = [ "$(location :load_path_in_runfiles_container_image)", diff --git a/ruby/tests/container_test.sh b/ruby/tests/container_test.sh index 35326ac..c19180a 100755 --- a/ruby/tests/container_test.sh +++ b/ruby/tests/container_test.sh @@ -7,15 +7,16 @@ if [[ $# -lt 2 ]]; then exit 1 fi -# check if we are running inside a Docker container, and skip this test if so. -if [[ -n "$(docker info 2>/dev/null| grep 'Cannot connect')" ]]; then +# check if we are running without access to Docker Server (eg, on CI +# within its own Docker container) and if so — skip this test. +if [[ -z $(command -v docker) || -n "$(docker info 2>/dev/null | grep 'Cannot connect')" ]]; then echo "No Docker runtime detected, skipping tests." exit 0 else CONTAINER_IMAGE_LOADER="$1" CONTAINER_IMAGE_NAME="$2" - if [[ -n $(command -v ${CONTAINER_IMAGE_LOADER}) ]] ; then + if [[ -n $(command -v ${CONTAINER_IMAGE_LOADER}) ]]; then ${CONTAINER_IMAGE_LOADER} docker run "${CONTAINER_IMAGE_NAME}" else @@ -23,5 +24,3 @@ else exit 2 fi fi - - diff --git a/ruby/tests/load_path_in_runfiles_test.rb b/ruby/tests/load_path_in_runfiles_test.rb index cbc0b02..9521827 100644 --- a/ruby/tests/load_path_in_runfiles_test.rb +++ b/ruby/tests/load_path_in_runfiles_test.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require 'ruby/tests/testdata/foo/g' -require 'external/bazelruby_ruby_rules_ruby_tests_testdata_another_workspace/baz/qux/j' +require 'external/bazelruby_rules_ruby_ruby_tests_testdata_another_workspace/baz/qux/j' [g, j] diff --git a/ruby/tests/testdata/another_workspace/WORKSPACE b/ruby/tests/testdata/another_workspace/WORKSPACE index 25cbfaa..86ed1c1 100644 --- a/ruby/tests/testdata/another_workspace/WORKSPACE +++ b/ruby/tests/testdata/another_workspace/WORKSPACE @@ -1,5 +1,5 @@ -workspace(name = "bazelruby_ruby_rules_ruby_tests_testdata_another_workspace") +workspace(name = "bazelruby_rules_ruby_ruby_tests_testdata_another_workspace") -load("@bazelruby_ruby_rules//ruby:defs.bzl", "ruby_register_toolchains") +load("@bazelruby_rules_ruby//ruby:defs.bzl", "rules_ruby_select_sdk") -ruby_register_toolchains() +rules_ruby_select_sdk() diff --git a/ruby/tests/testdata/another_workspace/baz/qux/BUILD.bazel b/ruby/tests/testdata/another_workspace/baz/qux/BUILD.bazel index 88a9927..89a2dfe 100644 --- a/ruby/tests/testdata/another_workspace/baz/qux/BUILD.bazel +++ b/ruby/tests/testdata/another_workspace/baz/qux/BUILD.bazel @@ -1,6 +1,6 @@ -package(default_visibility = ["//visibility:public"]) +load("@bazelruby_rules_ruby//ruby:defs.bzl", "ruby_library") -load("@bazelruby_ruby_rules//ruby:defs.bzl", "ruby_library") +package(default_visibility = ["//visibility:public"]) ruby_library( name = "j",