├── .bazelversion ├── .rules_version ├── ruby ├── private │ ├── BUILD.bazel │ ├── bundle │ │ ├── BUILD.bazel │ │ ├── activate_gems.rb │ │ ├── download_bundler.rb │ │ ├── create_bundle_build_file.rb │ │ └── bundle.bzl │ ├── toolchains │ │ ├── BUILD │ │ ├── interpreter_wrapper.tpl │ │ ├── BUILD.runtime.tpl │ │ ├── repository_context.bzl │ │ └── ruby_runtime.bzl │ ├── tools │ │ ├── BUILD.bazel │ │ ├── paths.bzl │ │ └── deps.bzl │ ├── rubocop │ │ ├── runner.sh.tpl │ │ ├── BUILD.bazel │ │ └── def.bzl │ ├── bundle.bzl │ ├── binary │ │ ├── BUILD.bazel │ │ ├── binary.bzl │ │ └── binary_wrapper.tpl │ ├── gem │ │ ├── BUILD.bazel │ │ ├── gemspec_template.tpl │ │ ├── gem.bzl │ │ ├── gemspec_builder.rb │ │ ├── gem_runner.rb │ │ └── gemspec.bzl │ ├── sdk.bzl │ ├── providers.bzl │ ├── gem.bzl │ ├── library.bzl │ ├── dependencies.bzl │ ├── rspec.bzl │ ├── constants.bzl │ └── toolchain.bzl ├── tests │ ├── testdata │ │ ├── bundled_commands │ │ │ ├── rake_golden │ │ │ ├── erb_golden │ │ │ ├── irb_golden │ │ │ ├── irb_input │ │ │ ├── erb_input │ │ │ └── rake_input │ │ ├── e.rb │ │ ├── f.rb │ │ ├── a.rb │ │ ├── b.rb │ │ ├── c.rb │ │ ├── d.rb │ │ ├── bar │ │ │ └── i.rb │ │ ├── another_workspace │ │ │ ├── baz │ │ │ │ └── qux │ │ │ │ │ ├── k.rb │ │ │ │ │ ├── j.rb │ │ │ │ │ └── BUILD.bazel │ │ │ └── WORKSPACE │ │ ├── foo │ │ │ ├── g.rb │ │ │ └── h.rb │ │ └── BUILD.bazel │ ├── runtime_run_rb_test.sh │ ├── ext_test.rb │ ├── gemspec │ │ ├── lib │ │ │ ├── example_gem.rb │ │ │ ├── foo │ │ │ │ ├── bar.rb │ │ │ │ └── BUILD │ │ │ └── BUILD │ │ ├── expected │ │ │ ├── strip_path_gem.gemspec │ │ │ ├── licensed_gem.gemspec │ │ │ ├── require_gem.gemspec │ │ │ └── example_gem.gemspec │ │ ├── gemspec_test.bzl │ │ └── BUILD.bazel │ ├── integration │ │ ├── Gemfile │ │ ├── script.rb │ │ ├── lib │ │ │ ├── BUILD.bazel │ │ │ └── foo.rb │ │ ├── WORKSPACE │ │ ├── spec │ │ │ ├── lib │ │ │ │ └── foo_spec.rb │ │ │ ├── spec_helper.rb │ │ │ └── script_spec.rb │ │ ├── BUILD.bazel │ │ └── Gemfile.lock │ ├── load_path_in_runfiles_test.rb │ ├── args_check.rb │ ├── example_ext.c │ ├── container_test.sh │ ├── diff_test.rb │ ├── include_order_check.rb │ └── BUILD.bazel ├── BUILD.bazel ├── deps.bzl └── defs.bzl ├── examples ├── simple_script │ ├── .ruby-version │ ├── .gitignore │ ├── Gemfile │ ├── .rubocop.yml │ ├── script.rb │ ├── lib │ │ ├── BUILD │ │ └── foo.rb │ ├── spec │ │ ├── script_spec.rb │ │ ├── lib │ │ │ └── foo_spec.rb │ │ └── spec_helper.rb │ ├── README.md │ ├── WORKSPACE │ ├── BUILD.bazel │ └── Gemfile.lock ├── simple_rails_api │ ├── .ruby-version │ ├── config │ │ ├── routes.rb │ │ ├── environment.rb │ │ ├── database.yml │ │ ├── application.rb │ │ ├── puma.rb │ │ └── environments │ │ │ └── development.rb │ ├── bin │ │ └── rails │ ├── app │ │ └── controllers │ │ │ ├── application_controller.rb │ │ │ └── home_controller.rb │ ├── config.ru │ ├── BUILD.bazel │ ├── WORKSPACE │ ├── .gitignore │ ├── README.md │ ├── Gemfile │ └── Gemfile.lock └── example_gem │ ├── lib │ ├── example_gem.rb │ ├── foo │ │ ├── bar.rb │ │ └── BUILD │ └── BUILD │ ├── BUILD │ └── WORKSPACE ├── .bazelignore ├── docs ├── troubleshooting.md ├── faq.md └── rules_ruby_architecture.md ├── Gemfile ├── .gitignore ├── .rubocop.yml ├── BUILD.bazel ├── .circleci ├── .bazelrc └── config.yml ├── Gemfile.lock ├── .github └── CODEOWNERS ├── bin └── setup-darwin ├── WORKSPACE ├── LICENSE └── README.md /.bazelversion: -------------------------------------------------------------------------------- 1 | 3.0.0 2 | -------------------------------------------------------------------------------- /.rules_version: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /ruby/private/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple_script/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 -------------------------------------------------------------------------------- /examples/simple_script/.gitignore: -------------------------------------------------------------------------------- 1 | /bazel-* 2 | -------------------------------------------------------------------------------- /.bazelignore: -------------------------------------------------------------------------------- 1 | examples 2 | ruby/tests/integration 3 | -------------------------------------------------------------------------------- /examples/simple_rails_api/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 2 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/rake_golden: -------------------------------------------------------------------------------- 1 | bar 2 | foo 3 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | Coming soon. 4 | 5 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/erb_golden: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | baz 4 | -------------------------------------------------------------------------------- /ruby/tests/testdata/e.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # nothing 3 | -------------------------------------------------------------------------------- /ruby/tests/testdata/f.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # nothing 3 | -------------------------------------------------------------------------------- /ruby/tests/testdata/a.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'd' 4 | -------------------------------------------------------------------------------- /ruby/tests/testdata/b.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'd' 4 | -------------------------------------------------------------------------------- /ruby/tests/testdata/c.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'e' 4 | -------------------------------------------------------------------------------- /ruby/tests/testdata/d.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'e' 4 | -------------------------------------------------------------------------------- /ruby/private/bundle/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | -------------------------------------------------------------------------------- /ruby/private/toolchains/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | -------------------------------------------------------------------------------- /ruby/private/tools/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/irb_golden: -------------------------------------------------------------------------------- 1 | => T 2 | => # 3 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bar/i.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def i 4 | :i 5 | end 6 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/irb_input: -------------------------------------------------------------------------------- 1 | T = Struct.new(:a, :b, :c) 2 | T.new(1, 2, 3) 3 | -------------------------------------------------------------------------------- /ruby/tests/runtime_run_rb_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | external/org_ruby_lang_ruby_toolchain/ruby_bin $* 3 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/erb_input: -------------------------------------------------------------------------------- 1 | % values.split(',').each do |value| 2 | <%= value %> 3 | % end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'rubocop', '~> 0.78' 6 | -------------------------------------------------------------------------------- /ruby/tests/testdata/another_workspace/baz/qux/k.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def k 4 | :k 5 | end 6 | -------------------------------------------------------------------------------- /ruby/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | toolchain_type(name = "toolchain_type") 4 | -------------------------------------------------------------------------------- /ruby/private/rubocop/runner.sh.tpl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | ( 3 | cd $BUILD_WORKSPACE_DIRECTORY && \ 4 | {{BIN}} $@ 5 | ) -------------------------------------------------------------------------------- /ruby/tests/ext_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'ruby/tests/example_ext' 4 | 5 | show_version 6 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/lib/example_gem.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'foo/bar' 4 | 5 | puts 'Foo' 6 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ — Frequently Asked Questions 2 | 3 | Coming soon. 4 | 5 | Also see: [Troubleshooting](troubleshooting.md). 6 | -------------------------------------------------------------------------------- /examples/example_gem/lib/example_gem.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'foo/bar' 4 | 5 | puts 'Foo' 6 | -------------------------------------------------------------------------------- /ruby/tests/testdata/another_workspace/baz/qux/j.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'qux/k' 4 | 5 | def j 6 | k 7 | end 8 | -------------------------------------------------------------------------------- /ruby/tests/testdata/foo/g.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'ruby/tests/testdata/foo/h' 4 | 5 | def g 6 | H.h 7 | end 8 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | get '/', to: 'home#home' 5 | end 6 | -------------------------------------------------------------------------------- /examples/simple_rails_api/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = Dir.pwd + '/config/application' 5 | 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /ruby/private/bundle.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | "//ruby/private/bundle:bundle.bzl", 3 | _rb_bundle = "rb_bundle", 4 | ) 5 | 6 | bundle_install = _rb_bundle 7 | rb_bundle = _rb_bundle 8 | -------------------------------------------------------------------------------- /ruby/tests/testdata/bundled_commands/rake_input: -------------------------------------------------------------------------------- 1 | task :foo => :bar do 2 | puts 'foo' 3 | end 4 | 5 | task :bar do 6 | puts 'bar' 7 | end 8 | 9 | # vim: set ft=ruby : 10 | -------------------------------------------------------------------------------- /ruby/tests/testdata/foo/h.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bar/i' 4 | 5 | module H 6 | module_function 7 | 8 | def h 9 | i 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /examples/example_gem/lib/foo/bar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Foo 4 | class Bar 5 | def call 6 | puts 'Hello World' 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/lib/foo/bar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Foo 4 | class Bar 5 | def call 6 | puts 'Hello World' 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ruby/private/rubocop/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | 3 | exports_files( 4 | ["runner.sh.tpl"], 5 | visibility = ["//visibility:public"], 6 | ) 7 | -------------------------------------------------------------------------------- /examples/simple_script/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'awesome_print' 6 | gem 'rspec', '~> 3.7.0' 7 | gem 'rspec-its' 8 | gem 'rubocop', '~> 0.78.0' 9 | -------------------------------------------------------------------------------- /ruby/tests/integration/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'awesome_print' 6 | gem 'rspec', '~> 3.7.0' 7 | gem 'rspec-its' 8 | gem 'rubocop', '~> 0.78.0' 9 | -------------------------------------------------------------------------------- /examples/simple_rails_api/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::API 4 | include ActionController::MimeResponds 5 | end 6 | -------------------------------------------------------------------------------- /ruby/private/binary/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | 3 | exports_files( 4 | [ 5 | "binary_wrapper.tpl", 6 | ], 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | -------------------------------------------------------------------------------- /examples/simple_script/.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.6 3 | UseCache: true 4 | DefaultFormatter: progress 5 | DisplayStyleGuide: true 6 | DisplayCopNames: true 7 | Exclude: 8 | - "bazel-*/**/*" -------------------------------------------------------------------------------- /ruby/tests/load_path_in_runfiles_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'ruby/tests/testdata/foo/g' 4 | require 'external/coinbase_rules_ruby_ruby_tests_testdata_another_workspace/baz/qux/j' 5 | 6 | [g, j] 7 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bazel Ignores 2 | **/bazel-* 3 | .bazelrc.user 4 | 5 | # Ruby Ignores 6 | **/.bundle 7 | **/vendor/bundle/** 8 | 9 | # OS Ignores 10 | **/.DS_Store 11 | 12 | # Editor Ignores 13 | **/.vscode 14 | **/.idea 15 | *.swp 16 | -------------------------------------------------------------------------------- /ruby/tests/testdata/another_workspace/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby_ruby_tests_testdata_another_workspace") 2 | 3 | load("@coinbase_rules_ruby//ruby:defs.bzl", "ruby_register_toolchains") 4 | 5 | ruby_register_toolchains() 6 | -------------------------------------------------------------------------------- /docs/rules_ruby_architecture.md: -------------------------------------------------------------------------------- 1 | This is the document that represents the current state of things in the `rules_ruby` repository, and will be updated as major changes happen. 2 | 3 | Check back here often 4 | 5 | # Ruby Rules Architecture & Design 6 | -------------------------------------------------------------------------------- /examples/simple_script/script.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'openssl' 4 | require 'awesome_print' 5 | 6 | require 'lib/foo' 7 | 8 | def oss_rand 9 | OpenSSL::BN.rand(512).to_s 10 | end 11 | 12 | puts Foo.aha + ' ' + oss_rand 13 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/lib/foo/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "default_library", 10 | srcs = ["bar.rb"], 11 | ) 12 | -------------------------------------------------------------------------------- /examples/simple_script/lib/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "foo", 10 | srcs = ["foo.rb"], 11 | ) 12 | -------------------------------------------------------------------------------- /examples/simple_script/spec/script_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'script' 5 | 6 | describe 'oss_rand' do 7 | it 'generates a String' do 8 | expect(oss_rand).to be_a_kind_of String 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ruby/tests/args_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Checks if args to rb_binary rules propagate to the actual 4 | # ruby processes 5 | 6 | expected = %w[foo bar baz] 7 | 8 | raise "Expected ARGV to be #{expected}; got #{ARGV}" unless ARGV == expected 9 | -------------------------------------------------------------------------------- /ruby/tests/example_ext.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static VALUE show_version(VALUE self) { 4 | ruby_show_version(); 5 | return Qnil; 6 | } 7 | 8 | void Init_example_ext(void) { 9 | rb_define_global_function("show_version", show_version, 0); 10 | } 11 | -------------------------------------------------------------------------------- /examples/example_gem/lib/foo/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "default_library", 10 | srcs = ["bar.rb"], 11 | ) 12 | -------------------------------------------------------------------------------- /ruby/private/bundle/activate_gems.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.each do |spec| 4 | # Don't activate default gems because they may duplicate gems 5 | # we already have (e.g. ruby bundler vs gem bundler) 6 | spec.activate unless spec.default_gem? 7 | end 8 | -------------------------------------------------------------------------------- /ruby/private/gem/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//ruby/private:__pkg__"]) 2 | 3 | exports_files( 4 | [ 5 | "gemspec_template.tpl", 6 | "gem_runner.rb", 7 | "gemspec_builder.rb", 8 | ], 9 | visibility = ["//visibility:public"], 10 | ) 11 | -------------------------------------------------------------------------------- /ruby/tests/integration/script.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'openssl' 4 | require 'awesome_print' 5 | 6 | require 'lib/foo' 7 | 8 | def oss_rand 9 | OpenSSL::BN.rand(512).to_s 10 | end 11 | 12 | def output 13 | ap Foo.aha + ' ' + oss_rand 14 | end 15 | 16 | output 17 | -------------------------------------------------------------------------------- /examples/example_gem/lib/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "example_gem", 10 | srcs = ["example_gem.rb"], 11 | deps = ["//lib/foo:default_library"], 12 | ) 13 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/lib/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "example_gem", 10 | srcs = ["example_gem.rb"], 11 | deps = ["//ruby/tests/gemspec/lib/foo:default_library"], 12 | ) 13 | -------------------------------------------------------------------------------- /examples/simple_rails_api/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | def home 5 | respond_to do |format| 6 | format.html { render json: 'hello' } 7 | format.json { render json: { message: 'hello' } } 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ruby/tests/integration/lib/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_library", 6 | ) 7 | 8 | rb_library( 9 | name = "foo", 10 | srcs = ["foo.rb"], 11 | deps = [ 12 | "@integration_test_bundle//:awesome_print", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /ruby/private/toolchains/interpreter_wrapper.tpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ -n "${RUNFILES_DIR+x}" ]; then 4 | PATH_PREFIX=$RUNFILES_DIR/{workspace_name}/ 5 | elif [ -d $0.runfiles ]; then 6 | PATH_PREFIX=`cd $0.runfiles; pwd`/{workspace_name}/ 7 | else 8 | PATH_PREFIX=`cd $(dirname $0); pwd`/ 9 | fi 10 | 11 | exec ${PATH_PREFIX}{rel_interpreter_path} "$@" -------------------------------------------------------------------------------- /examples/simple_script/README.md: -------------------------------------------------------------------------------- 1 | # Simple Script Example 2 | 3 | This Workspace includes a simple ruby script that includes and external gem and an internal library 4 | 5 | 6 | ### Bundle 7 | 8 | Update gemfile using 9 | 10 | ``` 11 | bundle lock --update 12 | ``` 13 | 14 | ### Rubocop 15 | Run rubocop autocorrect with: 16 | 17 | ``` 18 | bazel run //:rubocop -- -a 19 | ``` -------------------------------------------------------------------------------- /ruby/tests/testdata/another_workspace/baz/qux/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@coinbase_rules_ruby//ruby:defs.bzl", "rb_library") 4 | 5 | rb_library( 6 | name = "j", 7 | srcs = ["j.rb"], 8 | includes = ["baz"], 9 | deps = [":k"], 10 | ) 11 | 12 | rb_library( 13 | name = "k", 14 | srcs = ["k.rb"], 15 | ) 16 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 -------------------------------------------------------------------------------- /ruby/deps.bzl: -------------------------------------------------------------------------------- 1 | # Repository rules 2 | load( 3 | "@coinbase_rules_ruby//ruby/private:dependencies.bzl", 4 | _rules_ruby_dependencies = "rules_ruby_dependencies", 5 | ) 6 | load( 7 | "@coinbase_rules_ruby//ruby/private:sdk.bzl", 8 | _register_toolchains = "ruby_register_toolchains", 9 | ) 10 | 11 | rules_ruby_dependencies = _rules_ruby_dependencies 12 | 13 | ruby_register_toolchains = _register_toolchains 14 | -------------------------------------------------------------------------------- /ruby/private/gem/gemspec_template.tpl: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "{name}" 3 | s.summary = "{name}" 4 | s.authors = {authors} 5 | s.version = "{version}" 6 | s.licenses = {licenses} 7 | s.files = {srcs} 8 | s.require_paths = {require_paths} 9 | 10 | {gem_runtime_dependencies} 11 | end 12 | -------------------------------------------------------------------------------- /examples/example_gem/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_gem", 6 | ) 7 | 8 | rb_gem( 9 | name = "example_gem", 10 | authors = ["Coinbase"], 11 | gem_name = "example_gem", 12 | gem_runtime_dependencies = ["'example1', '~> 1.1', '>= 1.1.4'"], 13 | version = "0.1.0", 14 | deps = [ 15 | "//lib:example_gem", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/expected/strip_path_gem.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "strip_path_gem" 3 | s.summary = "strip_path_gem" 4 | s.authors = ["Coinbase"] 5 | s.version = "0.1.0" 6 | s.licenses = [] 7 | s.files = ["lib/foo/bar.rb", "lib/example_gem.rb"] 8 | s.require_paths = ["lib"] 9 | 10 | 11 | end 12 | -------------------------------------------------------------------------------- /ruby/private/tools/paths.bzl: -------------------------------------------------------------------------------- 1 | def strip_short_path(path, strip_paths): 2 | """ Given a short_path string will iterate over the 3 | list of strip_paths and remove any matches returning a 4 | new path string with no leading slash. 5 | """ 6 | if not strip_paths: 7 | return path 8 | 9 | for strip_path in strip_paths: 10 | if path.startswith(strip_path): 11 | return path.replace(strip_path, "").lstrip("/") 12 | return path 13 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/expected/licensed_gem.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "licensed_gem" 3 | s.summary = "licensed_gem" 4 | s.authors = ["Coinbase"] 5 | s.version = "0.1.0" 6 | s.licenses = ["MIT"] 7 | s.files = ["ruby/tests/gemspec/lib/foo/bar.rb", "ruby/tests/gemspec/lib/example_gem.rb"] 8 | s.require_paths = [] 9 | 10 | 11 | end 12 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.6 3 | UseCache: true 4 | DefaultFormatter: progress 5 | DisplayStyleGuide: true 6 | DisplayCopNames: true 7 | Exclude: 8 | - "bazel-*/**/*" 9 | - "ruby/tests/gemspec/expected/*" 10 | 11 | Layout/LineLength: 12 | Enabled: true 13 | Max: 200 14 | 15 | Style/Documentation: 16 | Enabled: false 17 | 18 | Metrics/MethodLength: 19 | Enabled: true 20 | Max: 20 21 | 22 | Metrics/AbcSize: 23 | Enabled: true 24 | Max: 40 25 | -------------------------------------------------------------------------------- /ruby/private/sdk.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | "@coinbase_rules_ruby//ruby/private/toolchains:ruby_runtime.bzl", 3 | _ruby_runtime = "ruby_runtime", 4 | ) 5 | 6 | def ruby_register_toolchains(version = "host"): 7 | """Registers ruby toolchains in the WORKSPACE file.""" 8 | 9 | _ruby_runtime( 10 | name = "org_ruby_lang_ruby_toolchain", 11 | version = version, 12 | ) 13 | 14 | native.register_toolchains( 15 | "@org_ruby_lang_ruby_toolchain//:toolchain", 16 | ) 17 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/expected/require_gem.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "require_gem" 3 | s.summary = "require_gem" 4 | s.authors = ["Coinbase"] 5 | s.version = "0.1.0" 6 | s.licenses = [] 7 | s.files = ["ruby/tests/gemspec/lib/foo/bar.rb", "ruby/tests/gemspec/lib/example_gem.rb"] 8 | s.require_paths = ["ruby/tests/gemspec/lib/foo"] 9 | 10 | 11 | end 12 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier") 2 | load("//ruby:defs.bzl", "rubocop") 3 | 4 | buildifier( 5 | name = "buildifier", 6 | exclude_patterns = ["./.git/**/*"], 7 | ) 8 | 9 | buildifier( 10 | name = "buildifier-check", 11 | exclude_patterns = ["./.git/**/*"], 12 | mode = "check", 13 | ) 14 | 15 | rubocop( 16 | name = "rubocop", 17 | bin = "@bundle//:bin/rubocop", 18 | deps = [ 19 | "@bundle//:rubocop", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /ruby/private/bundle/download_bundler.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'rubygems/gem_runner' 5 | 6 | def main 7 | # This should always be set by bundle.bzl 8 | bundler_version = ARGV[0] 9 | 10 | args = [ 11 | 'install', 12 | '--no-document', 13 | '--install-dir', 14 | 'bundler', 15 | "bundler:#{bundler_version}" 16 | ] 17 | 18 | begin 19 | Gem::GemRunner.new.run args 20 | rescue Gem::SystemExitException => e 21 | exit e.exit_code 22 | end 23 | end 24 | 25 | main if $PROGRAM_NAME == __FILE__ 26 | -------------------------------------------------------------------------------- /.circleci/.bazelrc: -------------------------------------------------------------------------------- 1 | # Startup options 2 | startup --max_idle_secs=1 3 | startup --host_jvm_args=-Xmx500m 4 | 5 | # Common options 6 | common --announce_rc 7 | common --color=yes 8 | common --verbose_failures 9 | common --show_progress 10 | common --show_progress_rate_limit=0.5 11 | 12 | # Build options 13 | build --spawn_strategy=standalone 14 | build --strategy=Genrule=standalone 15 | build --show_timestamps 16 | build --curses=no 17 | build --jobs=10 18 | 19 | # Test options 20 | test --spawn_strategy=standalone 21 | test --test_output=all 22 | test --test_verbose_timeout_warnings 23 | -------------------------------------------------------------------------------- /examples/example_gem/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby_example_gem") 2 | 3 | # Importing rules_ruby from the parent directory for developing 4 | # rules_ruby itself... 5 | local_repository( 6 | name = "coinbase_rules_ruby", 7 | path = "../..", 8 | ) 9 | 10 | load( 11 | "@coinbase_rules_ruby//ruby:deps.bzl", 12 | "ruby_register_toolchains", 13 | "rules_ruby_dependencies", 14 | ) 15 | 16 | rules_ruby_dependencies() 17 | 18 | ruby_register_toolchains() 19 | 20 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 21 | 22 | bazel_skylib_workspace() 23 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/expected/example_gem.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "example_gem" 3 | s.summary = "example_gem" 4 | s.authors = ["Coinbase"] 5 | s.version = "0.1.0" 6 | s.licenses = [] 7 | s.files = ["ruby/tests/gemspec/lib/foo/bar.rb", "ruby/tests/gemspec/lib/example_gem.rb"] 8 | s.require_paths = ["ruby/tests/gemspec/lib"] 9 | 10 | s.add_runtime_dependency 'example1', '~> 1.1', '>= 1.1.4' 11 | s.add_runtime_dependency 'example2', '~> 1.0' 12 | end 13 | -------------------------------------------------------------------------------- /ruby/private/providers.bzl: -------------------------------------------------------------------------------- 1 | RubyLibrary = provider( 2 | fields = [ 3 | "transitive_ruby_srcs", 4 | "ruby_incpaths", 5 | "rubyopt", 6 | ], 7 | ) 8 | 9 | RubyRuntimeContext = provider( 10 | doc = "Carries info required to execute Ruby Scripts", 11 | fields = [ 12 | "ctx", 13 | "interpreter", 14 | "environment", 15 | "bundler_version", 16 | ], 17 | ) 18 | 19 | RubyGem = provider( 20 | doc = "Carries info required to package a ruby gem", 21 | fields = [ 22 | "ctx", 23 | "version", 24 | "gemspec", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.0) 5 | jaro_winkler (1.5.4) 6 | parallel (1.19.1) 7 | parser (2.6.5.0) 8 | ast (~> 2.4.0) 9 | rainbow (3.0.0) 10 | rubocop (0.78.0) 11 | jaro_winkler (~> 1.5.1) 12 | parallel (~> 1.10) 13 | parser (>= 2.6) 14 | rainbow (>= 2.2.2, < 4.0) 15 | ruby-progressbar (~> 1.7) 16 | unicode-display_width (>= 1.4.0, < 1.7) 17 | ruby-progressbar (1.10.1) 18 | unicode-display_width (1.6.0) 19 | 20 | PLATFORMS 21 | ruby 22 | 23 | DEPENDENCIES 24 | rubocop (~> 0.78) 25 | 26 | BUNDLED WITH 27 | 2.0.2 28 | -------------------------------------------------------------------------------- /examples/simple_rails_api/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_binary", 6 | "rb_test", 7 | ) 8 | 9 | rb_binary( 10 | name = "server", 11 | srcs = glob( 12 | include = [ 13 | "app/**/*", 14 | "config/**/*", 15 | "bin/*", 16 | "config.ru", 17 | ], 18 | ), 19 | force_gem_pristine = [ 20 | "--extensions", 21 | ], 22 | main = ":bin/rails", 23 | deps = [ 24 | "@bundle//:listen", 25 | "@bundle//:pry-byebug", 26 | "@bundle//:rails", 27 | ], 28 | ) 29 | -------------------------------------------------------------------------------- /examples/simple_script/lib/foo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Foo is an example of a rb_library 4 | class Foo 5 | class << self 6 | def yell_aha 7 | puts aha 8 | end 9 | 10 | def aha 11 | 'You said, aha?' 12 | end 13 | 14 | def rot13(value) 15 | return nil unless value.is_a?(String) 16 | 17 | value.tr( 18 | 'abcdefghijklmnopqrstuvwxyz', 19 | 'nopqrstuvwxyzabcdefghijklm' 20 | ) 21 | end 22 | end 23 | 24 | attr_reader :goo, :foo 25 | 26 | def initialize(goo) 27 | @goo = goo 28 | @foo = transform(goo) 29 | end 30 | 31 | def transform(incoming = goo) 32 | Foo.rot13(incoming) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /ruby/tests/integration/lib/foo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Foo is an example of a rb_library 4 | class Foo 5 | class << self 6 | def yell_aha 7 | puts aha 8 | end 9 | 10 | def aha 11 | 'You said, aha?' 12 | end 13 | 14 | def rot13(value) 15 | return nil unless value.is_a?(String) 16 | 17 | value.tr( 18 | 'abcdefghijklmnopqrstuvwxyz', 19 | 'nopqrstuvwxyzabcdefghijklm' 20 | ) 21 | end 22 | end 23 | 24 | attr_reader :goo, :foo 25 | 26 | def initialize(goo) 27 | @goo = goo 28 | @foo = transform(goo) 29 | end 30 | 31 | def transform(incoming = goo) 32 | Foo.rot13(incoming) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /examples/simple_script/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby_example") 2 | 3 | # Importing rules_ruby from the parent directory for developing 4 | # rules_ruby itself... 5 | local_repository( 6 | name = "coinbase_rules_ruby", 7 | path = "../..", 8 | ) 9 | 10 | load( 11 | "@coinbase_rules_ruby//ruby:deps.bzl", 12 | "ruby_register_toolchains", 13 | "rules_ruby_dependencies", 14 | ) 15 | 16 | rules_ruby_dependencies() 17 | 18 | ruby_register_toolchains() 19 | 20 | load("@coinbase_rules_ruby//ruby:defs.bzl", "rb_bundle") 21 | 22 | rb_bundle( 23 | name = "bundle", 24 | bundler_version = "2.1.2", 25 | gemfile = "//:Gemfile", 26 | gemfile_lock = "//:Gemfile.lock", 27 | ) 28 | -------------------------------------------------------------------------------- /ruby/private/rubocop/def.bzl: -------------------------------------------------------------------------------- 1 | load("@coinbase_rules_ruby//ruby/private/binary:binary.bzl", "rb_binary") 2 | 3 | # This wraps an rb_binary in a script that is executed from the workspace folder 4 | def rubocop(name, bin, deps): 5 | bin_name = name + "-ruby" 6 | rb_binary( 7 | name = bin_name, 8 | main = bin, 9 | deps = deps, 10 | ) 11 | 12 | runner = "@coinbase_rules_ruby//ruby/private/rubocop:runner.sh.tpl" 13 | native.genrule( 14 | name = name, 15 | tools = [bin_name], 16 | srcs = [runner], 17 | executable = True, 18 | outs = [name + ".sh"], 19 | cmd = "sed \"s~{{BIN}}~$(location %s)~g\" $(location %s) > \"$@\"" % (bin_name, runner), 20 | ) 21 | -------------------------------------------------------------------------------- /ruby/tests/container_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [[ $# -lt 2 ]]; then 6 | echo "Usage: $0 CONTAINER_IMAGE_LOADER CONTAINER_IMAGE_NAME" >&2 7 | exit 1 8 | fi 9 | 10 | # check if we are running inside a Docker container, and skip this test if so. 11 | if [[ -n "$(docker info 2>/dev/null| grep 'Cannot connect')" ]]; then 12 | echo "No Docker runtime detected, skipping tests." 13 | exit 0 14 | else 15 | CONTAINER_IMAGE_LOADER="$1" 16 | CONTAINER_IMAGE_NAME="$2" 17 | 18 | if [[ -n $(command -v ${CONTAINER_IMAGE_LOADER}) ]] ; then 19 | ${CONTAINER_IMAGE_LOADER} 20 | docker run "${CONTAINER_IMAGE_NAME}" 21 | else 22 | echo "Command ${CONTAINER_IMAGE_LOADER} is invalid." 23 | exit 2 24 | fi 25 | fi 26 | 27 | 28 | -------------------------------------------------------------------------------- /ruby/tests/diff_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -w 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | 6 | def parse_flags(args) 7 | stdin = IO::NULL 8 | golden = nil 9 | parser = OptionParser.new do |opts| 10 | opts.on('--stdin=PATH') do |v| 11 | stdin = v 12 | end 13 | 14 | opts.on('--golden=PATH') do |v| 15 | golden = v 16 | end 17 | end 18 | args = parser.parse(args) 19 | 20 | [args, stdin, golden] 21 | end 22 | 23 | def main(args) 24 | args, stdin, golden = parse_flags(args) 25 | args << { in: stdin } 26 | stdout = IO.popen(args, &:read) 27 | 28 | IO.popen(['diff', '-B', '-c', golden, '-'], 'w') { |io| io << stdout } 29 | exit $CHILD_STATUS.exitstatus 30 | end 31 | 32 | main(ARGV) if $PROGRAM_NAME == __FILE__ 33 | -------------------------------------------------------------------------------- /ruby/tests/integration/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby_integration") 2 | 3 | # Importing rules_ruby from the parent directory for developing 4 | # rules_ruby itself... 5 | local_repository( 6 | name = "coinbase_rules_ruby", 7 | path = "../../..", 8 | ) 9 | 10 | load( 11 | "@coinbase_rules_ruby//ruby:deps.bzl", 12 | "ruby_register_toolchains", 13 | "rules_ruby_dependencies", 14 | ) 15 | 16 | rules_ruby_dependencies() 17 | 18 | ruby_register_toolchains() 19 | 20 | load("@coinbase_rules_ruby//ruby:defs.bzl", "rb_bundle") 21 | 22 | rb_bundle( 23 | # Name this not bundle to validate we aren't 24 | # hardcoding bundle 25 | name = "integration_test_bundle", 26 | gemfile = "//:Gemfile", 27 | gemfile_lock = "//:Gemfile.lock", 28 | ) 29 | -------------------------------------------------------------------------------- /examples/simple_rails_api/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby_example") 2 | 3 | # Importing rules_ruby from the parent directory for developing 4 | # rules_ruby itself... 5 | local_repository( 6 | name = "coinbase_rules_ruby", 7 | path = "../..", 8 | ) 9 | 10 | load( 11 | "@coinbase_rules_ruby//ruby:deps.bzl", 12 | "ruby_register_toolchains", 13 | "rules_ruby_dependencies", 14 | ) 15 | 16 | rules_ruby_dependencies() 17 | 18 | ruby_register_toolchains() 19 | 20 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 21 | 22 | bazel_skylib_workspace() 23 | 24 | load("@coinbase_rules_ruby//ruby:defs.bzl", "rb_bundle") 25 | 26 | rb_bundle( 27 | name = "bundle", 28 | gemfile = "//:Gemfile", 29 | gemfile_lock = "//:Gemfile.lock", 30 | ) 31 | -------------------------------------------------------------------------------- /examples/simple_rails_api/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | .byebug_history 25 | 26 | # Ignore master key for decrypting credentials and more. 27 | /config/master.key 28 | -------------------------------------------------------------------------------- /examples/simple_script/spec/lib/foo_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'lib/foo' 5 | 6 | RSpec.describe Foo do 7 | let(:goo) { 'Green slime was dripping down his throat into his lapdomen...' } 8 | subject(:foo) { Foo.new(goo) } 9 | 10 | context 'without the aha' do 11 | before { allow(Foo).to receive(:yell_aha).and_return('tiny dongle') } 12 | 13 | its(:goo) { should eq goo } 14 | its(:transform) { should_not eq goo } 15 | 16 | # Some rot13 old school encryption :) 17 | its(:transform) do 18 | should eq 'Gerra fyvzr jnf qevccvat qbja uvf guebng vagb uvf yncqbzra...' 19 | end 20 | end 21 | 22 | context 'aha' do 23 | it 'should print aha' do 24 | expect(Foo).to receive(:puts).with('You said, aha?').and_return(nil) 25 | Foo.yell_aha 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ruby/tests/integration/spec/lib/foo_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'lib/foo' 5 | 6 | RSpec.describe Foo do 7 | let(:goo) { 'Green slime was dripping down his throat into his lapdomen...' } 8 | subject(:foo) { Foo.new(goo) } 9 | 10 | context 'without the aha' do 11 | before { allow(Foo).to receive(:yell_aha).and_return('tiny dongle') } 12 | 13 | its(:goo) { should eq goo } 14 | its(:transform) { should_not eq goo } 15 | 16 | # Some rot13 old school encryption :) 17 | its(:transform) do 18 | should eq 'Gerra fyvzr jnf qevccvat qbja uvf guebng vagb uvf yncqbzra...' 19 | end 20 | end 21 | 22 | context 'aha' do 23 | it 'should print aha' do 24 | expect(Foo).to receive(:puts).with('You said, aha?').and_return(nil) 25 | Foo.yell_aha 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/gemspec_test.bzl: -------------------------------------------------------------------------------- 1 | load("@wspace_bazel_skylib//rules:diff_test.bzl", "diff_test") 2 | load("@wspace_bazel_skylib//rules:select_file.bzl", "select_file") 3 | load("//ruby:defs.bzl", "rb_gem", "rb_gemspec") 4 | 5 | def gemspec_test(name, **kwargs): 6 | if not name.endswith("_test"): 7 | fail("Gemspec test must end with '_test'") 8 | 9 | base_name = name.replace("_test", "") 10 | gem_name = "{}_gem".format(base_name) 11 | select_name = "select_{}_spec".format(base_name) 12 | gemspec_name = "{}.gemspec".format(gem_name) 13 | expected_gemsepc = ":expected/{}".format(gemspec_name) 14 | 15 | rb_gemspec( 16 | name = gem_name, 17 | **kwargs 18 | ) 19 | 20 | select_file( 21 | name = select_name, 22 | srcs = gem_name, 23 | subpath = gemspec_name, 24 | ) 25 | 26 | diff_test( 27 | name = name, 28 | file1 = select_name, 29 | file2 = expected_gemsepc, 30 | ) 31 | -------------------------------------------------------------------------------- /ruby/tests/integration/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_binary", 6 | "rb_library", 7 | "rb_rspec", 8 | "rb_test", 9 | "rubocop", 10 | ) 11 | 12 | # A ruby script that is depending on a Gem and another folders rb_library 13 | rb_binary( 14 | name = "bin", 15 | srcs = ["script.rb"], 16 | main = "script.rb", 17 | deps = [ 18 | "//lib:foo", 19 | ], 20 | ) 21 | 22 | rb_test( 23 | name = "rb_default_test", 24 | srcs = [ 25 | "script.rb", 26 | "//lib:foo", 27 | ] + glob([ 28 | "spec/**/*.rb", 29 | ]), 30 | args = [ 31 | "spec", 32 | ], 33 | main = "@integration_test_bundle//:bin/rspec", 34 | tags = [ 35 | "exclusive", 36 | "external", 37 | ], 38 | deps = [ 39 | "@integration_test_bundle//:rspec", 40 | "@integration_test_bundle//:rspec-its", 41 | ], 42 | ) 43 | -------------------------------------------------------------------------------- /ruby/tests/include_order_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Checks if require paths set by depender libraries appear earlier than 4 | # the ones by dependees. 5 | # i.e. -I options must be topologically sorted by library dependency as 6 | # Bundler does for gems. 7 | 8 | require 'ruby/tests/testdata/a' 9 | require 'ruby/tests/testdata/b' 10 | require 'ruby/tests/testdata/c' 11 | require 'ruby/tests/testdata/f' 12 | 13 | DEPS = [ 14 | # siblings 15 | %w[a b], 16 | %w[b c], 17 | %w[c f], 18 | 19 | # parent-child pairs 20 | %w[a d], 21 | %w[b d], 22 | %w[c e], 23 | %w[d e] 24 | ].freeze 25 | 26 | actual = $LOAD_PATH.grep(%r{/somewhere/.$}).map do |path| 27 | path.chars.last 28 | end 29 | 30 | raise 'Expect $LOAD_PATH includes somewhere/{a,b,c,d,e} but it did not' unless actual.sort == %w[a b c d e f] 31 | 32 | DEPS.each do |earlier, later| 33 | i = actual.index(earlier) 34 | j = actual.index(later) 35 | 36 | next if i < j 37 | 38 | raise "Expect #{earlier} precedes #{later} in #{actual} but did not" 39 | end 40 | -------------------------------------------------------------------------------- /ruby/private/gem.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | "//ruby/private/gem:gemspec.bzl", 3 | _rb_gemspec = "rb_gemspec", 4 | ) 5 | load( 6 | "//ruby/private/gem:gem.bzl", 7 | _rb_build_gem = "rb_build_gem", 8 | ) 9 | 10 | def rb_gem(name, version, gem_name, **kwargs): 11 | _gemspec_name = name + "_gemspec" 12 | deps = kwargs.get("deps", []) 13 | source_date_epoch = kwargs.pop("source_date_epoch", None) 14 | srcs = kwargs.pop("srcs", []) 15 | strip_paths = kwargs.pop("strip_paths", []) 16 | verbose = kwargs.pop("verbose", False) 17 | 18 | _rb_gemspec( 19 | name = _gemspec_name, 20 | gem_name = gem_name, 21 | version = version, 22 | strip_paths = strip_paths, 23 | **kwargs 24 | ) 25 | 26 | _rb_build_gem( 27 | name = name, 28 | gem_name = gem_name, 29 | gemspec = _gemspec_name, 30 | version = version, 31 | deps = srcs + deps, 32 | visibility = ["//visibility:public"], 33 | source_date_epoch = source_date_epoch, 34 | strip_paths = strip_paths, 35 | verbose = verbose, 36 | ) 37 | -------------------------------------------------------------------------------- /ruby/private/toolchains/BUILD.runtime.tpl: -------------------------------------------------------------------------------- 1 | load( 2 | "{rules_ruby_workspace}//ruby:defs.bzl", 3 | "rb_library", 4 | "ruby_toolchain", 5 | ) 6 | 7 | package(default_visibility = ["//visibility:public"]) 8 | 9 | ruby_toolchain( 10 | name = "toolchain", 11 | interpreter = "//:ruby_bin", 12 | rules_ruby_workspace = "{rules_ruby_workspace}", 13 | runtime = "//:runtime", 14 | # TODO(yugui) Extract platform info from RbConfig 15 | # exec_compatible_with = [], 16 | # target_compatible_with = [], 17 | ) 18 | 19 | sh_binary( 20 | name = "ruby_bin", 21 | srcs = ["ruby"], 22 | data = [":runtime"], 23 | ) 24 | 25 | cc_import( 26 | name = "libruby", 27 | hdrs = glob({hdrs}), 28 | shared_library = {shared_library}, 29 | static_library = {static_library}, 30 | ) 31 | 32 | cc_library( 33 | name = "headers", 34 | hdrs = glob({hdrs}), 35 | includes = {includes}, 36 | ) 37 | 38 | filegroup( 39 | name = "runtime", 40 | srcs = glob( 41 | include = ["**/*"], 42 | exclude = [ 43 | "BUILD.bazel", 44 | "WORKSPACE", 45 | ], 46 | ), 47 | ) 48 | 49 | # vim: set ft=bzl : 50 | -------------------------------------------------------------------------------- /ruby/defs.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | "@coinbase_rules_ruby//ruby/private:toolchain.bzl", 3 | _toolchain = "ruby_toolchain", 4 | ) 5 | load( 6 | "@coinbase_rules_ruby//ruby/private:library.bzl", 7 | _library = "rb_library", 8 | ) 9 | load( 10 | "@coinbase_rules_ruby//ruby/private/binary:binary.bzl", 11 | _binary = "rb_binary", 12 | _test = "rb_test", 13 | ) 14 | load( 15 | "@coinbase_rules_ruby//ruby/private:bundle.bzl", 16 | _rb_bundle = "rb_bundle", 17 | ) 18 | load( 19 | "@coinbase_rules_ruby//ruby/private:rspec.bzl", 20 | _rb_rspec = "rb_rspec", 21 | _rb_rspec_test = "rb_rspec_test", 22 | ) 23 | load( 24 | "@coinbase_rules_ruby//ruby/private/rubocop:def.bzl", 25 | _rubocop = "rubocop", 26 | ) 27 | load( 28 | "@coinbase_rules_ruby//ruby/private/gem:gemspec.bzl", 29 | _gemspec = "rb_gemspec", 30 | ) 31 | load( 32 | "@coinbase_rules_ruby//ruby/private:gem.bzl", 33 | _gem = "rb_gem", 34 | ) 35 | 36 | ruby_toolchain = _toolchain 37 | 38 | rb_library = _library 39 | rb_binary = _binary 40 | rb_test = _test 41 | rb_rspec_test = _rb_rspec_test 42 | rb_rspec = _rb_rspec 43 | rubocop = _rubocop 44 | bundle_install = _rb_bundle 45 | rb_bundle = _rb_bundle 46 | rb_gemspec = _gemspec 47 | rb_gem = _gem 48 | -------------------------------------------------------------------------------- /examples/simple_script/BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//:__subpackages__"]) 2 | 3 | load( 4 | "@coinbase_rules_ruby//ruby:defs.bzl", 5 | "rb_binary", 6 | "rb_library", 7 | "rb_rspec", 8 | "rb_test", 9 | "rubocop", 10 | ) 11 | 12 | # A ruby script that is depending on a Gem and another folders rb_library 13 | rb_binary( 14 | name = "bin", 15 | srcs = ["script.rb"], 16 | main = "script.rb", 17 | deps = [ 18 | "//lib:foo", 19 | "@bundle//:awesome_print", 20 | ], 21 | ) 22 | 23 | # This test example of how to run all tests with rspec. 24 | # TODO: make a more bazel-esk way to run each test individually 25 | rb_test( 26 | name = "all-specs", 27 | timeout = "short", 28 | srcs = [ 29 | "script.rb", 30 | "//lib:foo", 31 | ] + glob([ 32 | "spec/**/*.rb", 33 | ]), 34 | args = [ 35 | "spec", 36 | ], 37 | main = "@bundle//:bin/rspec", 38 | deps = [ 39 | "@bundle//:awesome_print", 40 | "@bundle//:rspec", 41 | "@bundle//:rspec-its", 42 | ], 43 | ) 44 | 45 | # Rubocop rule 46 | # To check 47 | # bazel run rubocop -- -a 48 | rubocop( 49 | name = "rubocop", 50 | bin = "@bundle//:bin/rubocop", 51 | deps = ["@bundle//:rubocop"], 52 | ) 53 | -------------------------------------------------------------------------------- /ruby/private/library.bzl: -------------------------------------------------------------------------------- 1 | load(":constants.bzl", "TOOLCHAIN_TYPE_NAME") 2 | load(":providers.bzl", "RubyLibrary") 3 | load( 4 | "//ruby/private/tools:deps.bzl", 5 | _transitive_deps = "transitive_deps", 6 | ) 7 | 8 | def _rb_library_impl(ctx): 9 | if not ctx.attr.srcs and not ctx.attr.deps: 10 | fail("At least srcs or deps must be present") 11 | 12 | deps = _transitive_deps(ctx) 13 | return [ 14 | DefaultInfo( 15 | default_runfiles = deps.default_files, 16 | data_runfiles = deps.data_files, 17 | files = deps.srcs, 18 | ), 19 | RubyLibrary( 20 | transitive_ruby_srcs = deps.srcs, 21 | ruby_incpaths = deps.incpaths, 22 | rubyopt = deps.rubyopt, 23 | ), 24 | ] 25 | 26 | rb_library = rule( 27 | implementation = _rb_library_impl, 28 | attrs = { 29 | "srcs": attr.label_list( 30 | allow_files = True, 31 | ), 32 | "includes": attr.string_list(), 33 | "rubyopt": attr.string_list(), 34 | "deps": attr.label_list( 35 | providers = [RubyLibrary], 36 | ), 37 | "data": attr.label_list( 38 | allow_files = True, 39 | ), 40 | }, 41 | toolchains = [TOOLCHAIN_TYPE_NAME], 42 | ) 43 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails' 4 | # Pick the frameworks you want: 5 | require 'active_model/railtie' 6 | require 'active_job/railtie' 7 | require 'active_record/railtie' 8 | require 'active_storage/engine' 9 | require 'action_controller/railtie' 10 | 11 | require 'action_text/engine' 12 | require 'action_view/railtie' 13 | require 'action_cable/engine' 14 | # require "sprockets/railtie" 15 | require 'rails/test_unit/railtie' 16 | 17 | module SimpleRailsApi 18 | class Application < Rails::Application 19 | # Initialize configuration defaults for originally generated Rails version. 20 | config.load_defaults 6.0 21 | config.root = Dir.pwd 22 | # Settings in config/environments/* take precedence over those specified here. 23 | # Application configuration can go into files in config/initializers 24 | # -- all .rb files in that directory are automatically loaded after loading 25 | # the framework and any gems in your application. 26 | 27 | # Only loads a smaller set of middleware suitable for API only apps. 28 | # Middleware like session, flash, cookies can be added back manually. 29 | # Skip views, helpers and assets when generating a new resource. 30 | config.api_only = true 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /examples/simple_script/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.0) 5 | awesome_print (1.8.0) 6 | diff-lcs (1.3) 7 | jaro_winkler (1.5.4) 8 | parallel (1.19.1) 9 | parser (2.6.5.0) 10 | ast (~> 2.4.0) 11 | rainbow (3.0.0) 12 | rspec (3.7.0) 13 | rspec-core (~> 3.7.0) 14 | rspec-expectations (~> 3.7.0) 15 | rspec-mocks (~> 3.7.0) 16 | rspec-core (3.7.1) 17 | rspec-support (~> 3.7.0) 18 | rspec-expectations (3.7.0) 19 | diff-lcs (>= 1.2.0, < 2.0) 20 | rspec-support (~> 3.7.0) 21 | rspec-its (1.3.0) 22 | rspec-core (>= 3.0.0) 23 | rspec-expectations (>= 3.0.0) 24 | rspec-mocks (3.7.0) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.7.0) 27 | rspec-support (3.7.1) 28 | rubocop (0.78.0) 29 | jaro_winkler (~> 1.5.1) 30 | parallel (~> 1.10) 31 | parser (>= 2.6) 32 | rainbow (>= 2.2.2, < 4.0) 33 | ruby-progressbar (~> 1.7) 34 | unicode-display_width (>= 1.4.0, < 1.7) 35 | ruby-progressbar (1.10.1) 36 | unicode-display_width (1.6.0) 37 | 38 | PLATFORMS 39 | ruby 40 | 41 | DEPENDENCIES 42 | awesome_print 43 | rspec (~> 3.7.0) 44 | rspec-its 45 | rubocop (~> 0.78.0) 46 | 47 | BUNDLED WITH 48 | 2.1.2 49 | -------------------------------------------------------------------------------- /ruby/tests/integration/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.1) 5 | awesome_print (1.8.0) 6 | diff-lcs (1.3) 7 | jaro_winkler (1.5.4) 8 | parallel (1.19.2) 9 | parser (2.7.1.4) 10 | ast (~> 2.4.1) 11 | rainbow (3.0.0) 12 | rspec (3.7.0) 13 | rspec-core (~> 3.7.0) 14 | rspec-expectations (~> 3.7.0) 15 | rspec-mocks (~> 3.7.0) 16 | rspec-core (3.7.1) 17 | rspec-support (~> 3.7.0) 18 | rspec-expectations (3.7.0) 19 | diff-lcs (>= 1.2.0, < 2.0) 20 | rspec-support (~> 3.7.0) 21 | rspec-its (1.3.0) 22 | rspec-core (>= 3.0.0) 23 | rspec-expectations (>= 3.0.0) 24 | rspec-mocks (3.7.0) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.7.0) 27 | rspec-support (3.7.1) 28 | rubocop (0.78.0) 29 | jaro_winkler (~> 1.5.1) 30 | parallel (~> 1.10) 31 | parser (>= 2.6) 32 | rainbow (>= 2.2.2, < 4.0) 33 | ruby-progressbar (~> 1.7) 34 | unicode-display_width (>= 1.4.0, < 1.7) 35 | ruby-progressbar (1.10.1) 36 | unicode-display_width (1.6.1) 37 | 38 | PLATFORMS 39 | ruby 40 | 41 | DEPENDENCIES 42 | awesome_print 43 | rspec (~> 3.7.0) 44 | rspec-its 45 | rubocop (~> 0.78.0) 46 | 47 | BUNDLED WITH 48 | 2.1.4 49 | -------------------------------------------------------------------------------- /ruby/private/dependencies.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 3 | 4 | def rules_ruby_dependencies(): 5 | if "bazel_skylib" not in native.existing_rules(): 6 | http_archive( 7 | name = "bazel_skylib", 8 | urls = [ 9 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", 10 | "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", 11 | ], 12 | sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", 13 | ) 14 | 15 | if "rules_pkg" not in native.existing_rules(): 16 | # Use Grahams improved rules_zip version until google merges it into mainline. 17 | # https://github.com/bazelbuild/rules_pkg/pull/127 18 | http_archive( 19 | name = "rules_pkg", 20 | url = "https://github.com/grahamjenson/rules_pkg/archive/3e0cd514ad1cdd2d23ab3d427d34436f75060018.zip", 21 | sha256 = "85e26971904cbb387688bd2a9e87c105f7cd7d986dc1b96bb1391924479c5ef6", 22 | strip_prefix = "rules_pkg-3e0cd514ad1cdd2d23ab3d427d34436f75060018/pkg", 23 | ) 24 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | 9 | # precedence. When someone opens a pull request that only 10 | # modifies JS files, only @js-owner and not the global 11 | # owner(s) will be requested for a review. 12 | #*.js @js-owner 13 | 14 | # You can also use email addresses if you prefer. They'll be 15 | # used to look up users just like we do for commit author 16 | # emails. 17 | #*.go docs@example.com 18 | 19 | # In this example, @doctocat owns any files in the build/logs 20 | # directory at the root of the repository and any of its 21 | # subdirectories. 22 | #/build/logs/ @doctocat 23 | 24 | # The `docs/*` pattern will match files like 25 | # `docs/getting-started.md` but not further nested files like 26 | # `docs/build-app/troubleshooting.md`. 27 | #docs/* docs@example.com 28 | 29 | # In this example, @octocat owns any file in an apps directory 30 | # anywhere in your repository. 31 | #apps/ @octocat 32 | 33 | # In this example, @doctocat owns any file in the `/docs` 34 | # directory in the root of your repository. 35 | #/docs/ @doctocat 36 | -------------------------------------------------------------------------------- /examples/simple_script/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Sets HOME here because: 4 | # If otherwise, it causes a runtime failure with the following steps. 5 | # 1. RSpec::Core::ConfigurationOptions.global_options_file raises an exception 6 | # because $HOME is not set in the sandbox environment of Bazel 7 | # 2. the rescue clause calls RSpec::Support.#warning 8 | # 3. #warning calls #warn_with 9 | # 4. #warn_with tries to lookup the first caller which is not a part of RSpec. 10 | # But all the call stack entires are about RSpec at this time because 11 | # it is invoked by rpsec/autorun. So #warn_with raises an exception 12 | # 5. The process fails with an unhandled exception. 13 | 14 | ENV['HOME'] ||= '/' 15 | 16 | require 'rspec' 17 | require 'rspec/its' 18 | require 'awesome_print' 19 | 20 | RSpec.configure do |config| 21 | config.expect_with :rspec do |expectations| 22 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 23 | end 24 | 25 | config.mock_with :rspec do |mocks| 26 | mocks.verify_partial_doubles = true 27 | end 28 | 29 | config.shared_context_metadata_behavior = :apply_to_host_groups 30 | 31 | config.warnings = true 32 | config.filter_run_when_matching :focus 33 | # config.disable_monkey_patching! 34 | config.order = :random 35 | Kernel.srand config.seed 36 | end 37 | -------------------------------------------------------------------------------- /ruby/tests/integration/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Sets HOME here because: 4 | # If otherwise, it causes a runtime failure with the following steps. 5 | # 1. RSpec::Core::ConfigurationOptions.global_options_file raises an exception 6 | # because $HOME is not set in the sandbox environment of Bazel 7 | # 2. the rescue clause calls RSpec::Support.#warning 8 | # 3. #warning calls #warn_with 9 | # 4. #warn_with tries to lookup the first caller which is not a part of RSpec. 10 | # But all the call stack entires are about RSpec at this time because 11 | # it is invoked by rpsec/autorun. So #warn_with raises an exception 12 | # 5. The process fails with an unhandled exception. 13 | 14 | ENV['HOME'] ||= '/' 15 | 16 | require 'rspec' 17 | require 'rspec/its' 18 | require 'awesome_print' 19 | 20 | RSpec.configure do |config| 21 | config.expect_with :rspec do |expectations| 22 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 23 | end 24 | 25 | config.mock_with :rspec do |mocks| 26 | mocks.verify_partial_doubles = true 27 | end 28 | 29 | config.shared_context_metadata_behavior = :apply_to_host_groups 30 | 31 | config.warnings = true 32 | config.filter_run_when_matching :focus 33 | # config.disable_monkey_patching! 34 | config.order = :random 35 | Kernel.srand config.seed 36 | end 37 | -------------------------------------------------------------------------------- /ruby/tests/gemspec/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//ruby/tests/gemspec:gemspec_test.bzl", "gemspec_test") 2 | load("//ruby:defs.bzl", "rb_gem") 3 | 4 | gemspec_test( 5 | name = "licensed_test", 6 | authors = ["Coinbase"], 7 | gem_name = "licensed_gem", 8 | licenses = ["MIT"], 9 | version = "0.1.0", 10 | deps = [ 11 | "//ruby/tests/gemspec/lib:example_gem", 12 | ], 13 | ) 14 | 15 | gemspec_test( 16 | name = "example_test", 17 | authors = ["Coinbase"], 18 | gem_name = "example_gem", 19 | gem_runtime_dependencies = [ 20 | "'example1', '~> 1.1', '>= 1.1.4'", 21 | "'example2', '~> 1.0'", 22 | ], 23 | version = "0.1.0", 24 | deps = [ 25 | "//ruby/tests/gemspec/lib:example_gem", 26 | ], 27 | ) 28 | 29 | gemspec_test( 30 | name = "require_test", 31 | authors = ["Coinbase"], 32 | gem_name = "require_gem", 33 | require_paths = ["ruby/tests/gemspec/lib/foo"], 34 | version = "0.1.0", 35 | deps = [ 36 | "//ruby/tests/gemspec/lib:example_gem", 37 | ], 38 | ) 39 | 40 | gemspec_test( 41 | name = "strip_path_test", 42 | authors = ["Coinbase"], 43 | gem_name = "strip_path_gem", 44 | require_paths = ["lib"], 45 | strip_paths = ["ruby/tests/gemspec"], 46 | version = "0.1.0", 47 | deps = [ 48 | "//ruby/tests/gemspec/lib:example_gem", 49 | ], 50 | ) 51 | -------------------------------------------------------------------------------- /examples/simple_rails_api/README.md: -------------------------------------------------------------------------------- 1 | # Rails API App 2 | 3 | This example is meant to be an example of how to structure a rails application in a `bazel`-ish way. 4 | 5 | ## Run the Example 6 | 7 | To start the application run: 8 | 9 | ``` 10 | bazel run :server -- server 11 | ``` 12 | 13 | then call the `home_controller.rb#home` method with: 14 | 15 | ``` 16 | curl 127.0.0.1:3000 17 | ``` 18 | 19 | ## Creating the examples 20 | This application was created by running `rails new simple_rails_api --api`, adding the `BUILD` and `WORKSPACE` files, then executing the changes below. 21 | 22 | ### Deleting folders 23 | 24 | This is a simple example app so we deleted a lot of the folders, e.g. `vendor` `public` `test` `tmp` `db` `lib` `log` folders. 25 | 26 | ### Bundler 27 | 28 | Since we are importing the rails application, we do not want to run `bundler/setup` as this looks at the Gemfile provided. This means that any gem setup will have to be manual. 29 | 30 | ### Rails.root APP_PATH 31 | 32 | Because the directory the application being started is in the bazel-bin folder (not the source directory) we need to change some config. 33 | 34 | In `bin/rails` set: 35 | 36 | ``` 37 | APP_PATH = Dir.pwd + '/config/application' 38 | ``` 39 | 40 | and in `config/application.rb` add: 41 | 42 | ``` 43 | config.root = Dir.pwd 44 | ``` 45 | 46 | ### Add simple home controller 47 | 48 | Added a simple home controller to make sure everything was working correctly 49 | 50 | -------------------------------------------------------------------------------- /ruby/tests/testdata/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load( 2 | "//ruby:defs.bzl", 3 | "rb_library", 4 | ) 5 | 6 | package(default_visibility = ["//ruby/tests:__pkg__"]) 7 | 8 | exports_files([ 9 | "bundled_commands/erb_golden", 10 | "bundled_commands/erb_input", 11 | "bundled_commands/irb_golden", 12 | "bundled_commands/irb_input", 13 | "bundled_commands/rake_golden", 14 | "bundled_commands/rake_input", 15 | ]) 16 | 17 | # 18 | # Dependency Graph:: 19 | # 20 | # a --+--d--+--e 21 | # | | 22 | # b --+ | 23 | # | 24 | # c---+-----+ 25 | # 26 | # f 27 | # 28 | # g -----h-----i 29 | 30 | rb_library( 31 | name = "a", 32 | srcs = ["a.rb"], 33 | includes = ["somewhere/a"], 34 | deps = [":d"], 35 | ) 36 | 37 | rb_library( 38 | name = "b", 39 | srcs = ["b.rb"], 40 | includes = ["somewhere/b"], 41 | deps = [":d"], 42 | ) 43 | 44 | rb_library( 45 | name = "c", 46 | srcs = ["c.rb"], 47 | includes = ["somewhere/c"], 48 | deps = [":e"], 49 | ) 50 | 51 | rb_library( 52 | name = "d", 53 | srcs = ["d.rb"], 54 | includes = ["somewhere/d"], 55 | deps = [":e"], 56 | ) 57 | 58 | rb_library( 59 | name = "e", 60 | srcs = ["e.rb"], 61 | includes = ["somewhere/e"], 62 | ) 63 | 64 | rb_library( 65 | name = "f", 66 | srcs = ["f.rb"], 67 | includes = ["somewhere/f"], 68 | ) 69 | 70 | rb_library( 71 | name = "g", 72 | srcs = ["foo/g.rb"], 73 | deps = [":h"], 74 | ) 75 | 76 | rb_library( 77 | name = "h", 78 | srcs = ["foo/h.rb"], 79 | includes = ["ruby/tests/testdata"], 80 | deps = [":i"], 81 | ) 82 | 83 | rb_library( 84 | name = "i", 85 | srcs = ["bar/i.rb"], 86 | ) 87 | -------------------------------------------------------------------------------- /bin/setup-darwin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | #—————————————————————————————— OS-X SPECIFIC INSTALLERS ————————————————————————— 5 | 6 | __setup::brew-validate() { 7 | # Homebrew is required to install Bazel 8 | if ! brew help >/dev/null; then 9 | echo "brew is not installed, please install from https://brew.sh" 10 | exit 1 11 | else 12 | echo "Homebrew is already installed." 13 | fi 14 | } 15 | 16 | __setup::brew-install-jdk() { 17 | # The JDK homebrew cask is a required to install the java dependencies for Bazel 18 | if ! brew cask list | grep -q openjdk; then 19 | eval "brew cask install homebrew/cask-versions/adoptopenjdk8" 20 | else 21 | echo "JDK already installed." 22 | fi 23 | } 24 | 25 | __setup::brew-deps() { 26 | echo "Attempting to install brew-deps" 27 | eval "brew install xz ydiff" 28 | } 29 | 30 | __setup::is-bazelisk-installed() { 31 | /bin/ls -al "$(command -v bazel)" | grep -q bazelisk 32 | } 33 | 34 | #—————————————————————————————— PUBLIC INTERFACE ————————————————————————— 35 | 36 | setup::brew() { 37 | __setup::brew-validate 38 | __setup::brew-install-jdk 39 | __setup::brew-deps 40 | } 41 | 42 | setup::xcode-tools() { 43 | # xcode command line tools are required, specifically gcc 44 | # if xcode already exists, this command will error, otherwise prompt the user 45 | if [[ -n $(xcode-select --install 2>&1) ]]; then 46 | echo "xcode-select tools are already installed." 47 | fi 48 | } 49 | 50 | setup::bazelisk() { 51 | if __setup::is-bazelisk-installed; then 52 | echo "Bazelisk is already installed." 53 | else 54 | eval "brew install bazelbuild/tap/bazelisk" 55 | fi 56 | } 57 | 58 | setup::darwin() { 59 | setup::xcode-tools 60 | setup::brew 61 | setup::bazelisk 62 | } 63 | -------------------------------------------------------------------------------- /examples/simple_rails_api/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby '2.6.5' 7 | 8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 9 | gem 'rails', '~> 6.0.2' 10 | # Use sqlite3 as the database for Active Record 11 | gem 'sqlite3', '~> 1.4' 12 | # Use Puma as the app server 13 | gem 'listen', '>= 3.0.5', '< 3.2' 14 | gem 'puma', '~> 4.3' 15 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 16 | # gem 'jbuilder', '~> 2.7' 17 | # Use Redis adapter to run Action Cable in production 18 | # gem 'redis', '~> 4.0' 19 | # Use Active Model has_secure_password 20 | # gem 'bcrypt', '~> 3.1.7' 21 | 22 | # Use Active Storage variant 23 | # gem 'image_processing', '~> 1.2' 24 | 25 | # CVE-2020-8185 26 | gem 'actionpack', '>= 6.0.3.3' 27 | 28 | # CVE-2020-15169 29 | gem 'actionview', '>= 6.0.3.3' 30 | 31 | # Reduces boot times through caching; required in config/boot.rb 32 | gem 'bootsnap', '>= 1.4.2', require: false 33 | gem 'pry-byebug' 34 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible 35 | # gem 'rack-cors' 36 | 37 | group :development, :test do 38 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 39 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 40 | end 41 | 42 | group :development do 43 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 44 | gem 'spring' 45 | gem 'spring-watcher-listen', '~> 2.0.0' 46 | end 47 | 48 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 49 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 50 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } 10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 14 | # 15 | port ENV.fetch('PORT') { 3000 } 16 | 17 | # Specifies the `environment` that Puma will run in. 18 | # 19 | environment ENV.fetch('RAILS_ENV') { 'development' } 20 | 21 | # Specifies the `pidfile` that Puma will use. 22 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } 23 | 24 | # Specifies the number of `workers` to boot in clustered mode. 25 | # Workers are forked web server processes. If using threads and workers together 26 | # the concurrency of the application would be max `threads` * `workers`. 27 | # Workers do not work on JRuby or Windows (both of which do not support 28 | # processes). 29 | # 30 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 31 | 32 | # Use the `preload_app!` method when specifying a `workers` number. 33 | # This directive tells Puma to first boot the application and load code 34 | # before forking the application. This takes advantage of Copy On Write 35 | # process behavior so workers use less memory. 36 | # 37 | # preload_app! 38 | 39 | # Allow puma to be restarted by `rails restart` command. 40 | plugin :tmp_restart 41 | -------------------------------------------------------------------------------- /ruby/tests/integration/spec/script_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'script' 5 | 6 | describe 'oss_rand' do 7 | it 'generates a String' do 8 | expect(oss_rand).to be_a_kind_of String 9 | end 10 | end 11 | 12 | describe 'gem loading' do 13 | it 'only loads expected gems and their deps' do 14 | expect(Gem.loaded_specs.length).to eq(12) 15 | expect(Gem.loaded_specs.keys).to contain_exactly( 16 | 'awesome_print', 17 | 'diff-lcs', 18 | 'ipaddr', 19 | 'openssl', 20 | 'rspec', 21 | 'rspec-core', 22 | 'rspec-expectations', 23 | 'rspec-its', 24 | 'rspec-mocks', 25 | 'rspec-support', 26 | 'stringio', 27 | 'strscan' 28 | ) 29 | end 30 | 31 | it 'errors when requiring gems that are in Gemfile, but not included in lib rule' do 32 | expect { Gem::Specification.find_by_name('rubocop') }.to raise_error(Gem::MissingSpecError) 33 | end 34 | 35 | # bundler gets installed separately so we need to ensure it isn't accidentally being included 36 | it 'errors when requiring bundler because it is not in the lib rule' do 37 | expect { Gem::Specification.find_by_name('bundler') }.to raise_error(Gem::MissingSpecError) 38 | end 39 | end 40 | 41 | describe 'validate rb_env' do 42 | it 'sets GEM_HOME correctly' do 43 | expect(ENV['GEM_HOME']).to end_with('rb_default_test.runfiles/integration_test_bundle/lib/ruby/2.6.0') 44 | end 45 | 46 | it 'sets GEM_PATH correctly' do 47 | gem_paths = ENV['GEM_PATH'].split(':') 48 | expect(gem_paths.length).to eq(2) 49 | expect(gem_paths[0]).to end_with('rb_default_test.runfiles/integration_test_bundle/lib/ruby/2.6.0') 50 | expect(gem_paths[1]).to end_with('rb_default_test.runfiles/integration_test_bundle/bundler') 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /examples/simple_rails_api/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options). 31 | config.active_storage.service = :local 32 | 33 | # Print deprecation notices to the Rails logger. 34 | config.active_support.deprecation = :log 35 | 36 | # Raise an error on page load if there are pending migrations. 37 | config.active_record.migration_error = :page_load 38 | 39 | # Highlight code that triggered database queries in logs. 40 | config.active_record.verbose_query_logs = true 41 | 42 | # Raises error for missing translations. 43 | # config.action_view.raise_on_missing_translations = true 44 | 45 | # Use an evented file watcher to asynchronously detect changes in source code, 46 | # routes, locales, etc. This feature depends on the listen gem. 47 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 48 | end 49 | -------------------------------------------------------------------------------- /ruby/private/rspec.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | ":constants.bzl", 3 | "DEFAULT_BUNDLE_NAME", 4 | "DEFAULT_RSPEC_ARGS", 5 | "DEFAULT_RSPEC_GEMS", 6 | "RSPEC_ATTRS", 7 | "TOOLCHAIN_TYPE_NAME", 8 | ) 9 | load("//ruby/private/binary:binary.bzl", "rb_binary_macro") 10 | 11 | def rb_rspec( 12 | name, 13 | srcs, 14 | specs, 15 | deps = None, 16 | size = "small", 17 | rspec_args = None, # This is expected to be a dictionary 18 | bundle = DEFAULT_BUNDLE_NAME, 19 | visibility = None, 20 | **kwargs): 21 | if specs == None: 22 | specs = [] 23 | 24 | if srcs == None: 25 | srcs = [] 26 | 27 | if rspec_args == None: 28 | rspec_args = {} 29 | 30 | args_list = [] 31 | 32 | args_dict = {} 33 | args_dict.update(DEFAULT_RSPEC_ARGS) 34 | args_dict.update(rspec_args) 35 | 36 | # We pass the respec_args as a dictionary so that you can overwrite 37 | # the default rspec arguments with custom ones. 38 | for option, value in [(option, value) for option, value in args_dict.items()]: 39 | if value != None: 40 | args_list.append("%s %s" % (option, value)) 41 | else: 42 | args_list.append("%s" % (option)) 43 | 44 | args_list += specs 45 | 46 | rspec_gems = ["%s:%s" % (bundle, gem) for gem in DEFAULT_RSPEC_GEMS] 47 | 48 | deps += rspec_gems 49 | 50 | rb_rspec_test( 51 | name = name, 52 | visibility = visibility, 53 | args = args_list, 54 | srcs = srcs + specs, 55 | deps = deps, 56 | size = size, 57 | **kwargs 58 | ) 59 | 60 | def _rb_rspec_test_impl(ctx): 61 | return rb_binary_macro( 62 | ctx, 63 | ctx.file.rspec_executable, 64 | ctx.attr.srcs, 65 | ) 66 | 67 | rb_rspec_test = rule( 68 | implementation = _rb_rspec_test_impl, 69 | attrs = RSPEC_ATTRS, 70 | test = True, 71 | toolchains = [TOOLCHAIN_TYPE_NAME], 72 | ) 73 | -------------------------------------------------------------------------------- /ruby/private/constants.bzl: -------------------------------------------------------------------------------- 1 | load(":providers.bzl", "RubyLibrary") 2 | 3 | RULES_RUBY_WORKSPACE_NAME = "@coinbase_rules_ruby" 4 | TOOLCHAIN_TYPE_NAME = "%s//ruby:toolchain_type" % RULES_RUBY_WORKSPACE_NAME 5 | 6 | DEFAULT_RSPEC_ARGS = {"--format": "documentation", "--force-color": None} 7 | DEFAULT_RSPEC_GEMS = ["rspec", "rspec-its"] 8 | DEFAULT_BUNDLE_NAME = "@bundle//" 9 | 10 | RUBY_ATTRS = { 11 | "srcs": attr.label_list( 12 | allow_files = True, 13 | ), 14 | "deps": attr.label_list( 15 | providers = [RubyLibrary], 16 | ), 17 | "includes": attr.string_list(), 18 | "rubyopt": attr.string_list(), 19 | "data": attr.label_list( 20 | allow_files = True, 21 | ), 22 | "main": attr.label( 23 | allow_single_file = True, 24 | ), 25 | "force_gem_pristine": attr.string_list( 26 | doc = "Jank hack. Run gem pristine on some gems that don't handle symlinks well", 27 | ), 28 | "run_under": attr.string( 29 | doc = "Execute a directory change before running the binary.", 30 | ), 31 | "_wrapper_template": attr.label( 32 | allow_single_file = True, 33 | default = "//ruby/private/binary:binary_wrapper.tpl", 34 | ), 35 | "_misc_deps": attr.label_list( 36 | allow_files = True, 37 | default = ["@bazel_tools//tools/bash/runfiles"], 38 | ), 39 | } 40 | 41 | _RSPEC_ATTRS = { 42 | "bundle": attr.string( 43 | default = DEFAULT_BUNDLE_NAME, 44 | doc = "Name of the bundle where the rspec gem can be found, eg @bundle//", 45 | ), 46 | "rspec_args": attr.string_list( 47 | default = [], 48 | doc = "Arguments passed to rspec executable", 49 | ), 50 | "rspec_executable": attr.label( 51 | default = "%s:bin/rspec" % (DEFAULT_BUNDLE_NAME), 52 | allow_single_file = True, 53 | doc = "RSpec Executable Label", 54 | ), 55 | } 56 | 57 | RSPEC_ATTRS = {} 58 | 59 | RSPEC_ATTRS.update(RUBY_ATTRS) 60 | RSPEC_ATTRS.update(_RSPEC_ATTRS) 61 | -------------------------------------------------------------------------------- /ruby/private/toolchain.bzl: -------------------------------------------------------------------------------- 1 | load(":constants.bzl", "RULES_RUBY_WORKSPACE_NAME") 2 | 3 | RubyRuntimeInfo = provider( 4 | doc = "Information about a Ruby interpreter, related commands and libraries", 5 | fields = { 6 | "interpreter": "A label which points the Ruby interpreter", 7 | "bundler": "A label which points bundler command", 8 | "runtime": "A list of labels which points runtime libraries", 9 | "rubyopt": "A list of strings which should be passed to the interpreter as command line options", 10 | }, 11 | ) 12 | 13 | def _ruby_toolchain_impl(ctx): 14 | return [platform_common.ToolchainInfo( 15 | ruby_runtime = RubyRuntimeInfo( 16 | interpreter = ctx.attr.interpreter, 17 | runtime = ctx.files.runtime, 18 | rubyopt = ctx.attr.rubyopt, 19 | ), 20 | )] 21 | 22 | _ruby_toolchain = rule( 23 | implementation = _ruby_toolchain_impl, 24 | attrs = { 25 | "interpreter": attr.label( 26 | mandatory = True, 27 | allow_files = True, 28 | executable = True, 29 | cfg = "target", 30 | ), 31 | "runtime": attr.label( 32 | mandatory = True, 33 | allow_files = True, 34 | cfg = "target", 35 | ), 36 | "rubyopt": attr.string_list( 37 | default = [], 38 | ), 39 | }, 40 | ) 41 | 42 | def ruby_toolchain( 43 | name, 44 | interpreter, 45 | runtime, 46 | rubyopt = [], 47 | rules_ruby_workspace = RULES_RUBY_WORKSPACE_NAME, 48 | **kwargs): 49 | impl_name = name + "_sdk" 50 | _ruby_toolchain( 51 | name = impl_name, 52 | interpreter = interpreter, 53 | runtime = runtime, 54 | rubyopt = rubyopt, 55 | ) 56 | 57 | native.toolchain( 58 | name = name, 59 | toolchain_type = "%s//ruby:toolchain_type" % rules_ruby_workspace, 60 | toolchain = ":%s" % impl_name, 61 | **kwargs 62 | ) 63 | -------------------------------------------------------------------------------- /ruby/private/toolchains/repository_context.bzl: -------------------------------------------------------------------------------- 1 | # 2 | # Provides access to a Ruby SDK at the loading phase 3 | # (in a repository rule). 4 | # 5 | 6 | def _eval_ruby(ruby, script, options = None): 7 | arguments = [ruby.interpreter_realpath] 8 | if options: 9 | arguments.extend(options) 10 | arguments.extend(["-e", script]) 11 | 12 | environment = {"RUBYOPT": "--enable=gems"} 13 | 14 | result = ruby._ctx.execute(arguments, environment = environment) 15 | if result.return_code: 16 | message = "Failed to evaluate ruby snippet with {}: {}".format( 17 | ruby.interpreter_realpath, 18 | result.stderr, 19 | ) 20 | fail(message) 21 | return result.stdout 22 | 23 | def _rbconfig(ruby, name): 24 | options = ["-rrbconfig"] 25 | script = "print RbConfig::CONFIG[{}]".format( 26 | # Here we actually needs String#dump in Ruby but 27 | # repr in Python is compatible enough. 28 | repr(name), 29 | ) 30 | return _eval_ruby(ruby, script = script, options = options) 31 | 32 | def _expand_rbconfig(ruby, expr): 33 | options = ["-rrbconfig"] 34 | script = "print RbConfig.expand({})".format( 35 | # Here we actually needs String#dump in Ruby but 36 | # repr in Python is compatible enough. 37 | repr(expr), 38 | ) 39 | return _eval_ruby(ruby, script = script, options = options) 40 | 41 | def ruby_repository_context(repository_ctx, interpreter_path): 42 | interpreter_path = interpreter_path.realpath 43 | interpreter_name = interpreter_path.basename 44 | 45 | rel_interpreter_path = str(interpreter_path) 46 | if rel_interpreter_path.startswith("/"): 47 | rel_interpreter_path = rel_interpreter_path[1:] 48 | 49 | return struct( 50 | # Location of the interpreter 51 | rel_interpreter_path = rel_interpreter_path, 52 | interpreter_name = interpreter_path.basename, 53 | interpreter_realpath = interpreter_path, 54 | 55 | # Standard repository structure for ruby runtimes 56 | 57 | # Helper methods 58 | eval = _eval_ruby, 59 | rbconfig = _rbconfig, 60 | expand_rbconfig = _expand_rbconfig, 61 | _ctx = repository_ctx, 62 | ) 63 | -------------------------------------------------------------------------------- /ruby/private/tools/deps.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//lib:paths.bzl", "paths") 2 | load( 3 | "//ruby/private:providers.bzl", 4 | "RubyLibrary", 5 | ) 6 | 7 | def _transitive_srcs(deps): 8 | return struct( 9 | srcs = [d[RubyLibrary].transitive_ruby_srcs for d in deps if RubyLibrary in d], 10 | incpaths = [d[RubyLibrary].ruby_incpaths for d in deps if RubyLibrary in d], 11 | rubyopt = [d[RubyLibrary].rubyopt for d in deps if RubyLibrary in d], 12 | data_files = [d[DefaultInfo].data_runfiles.files for d in deps], 13 | default_files = [d[DefaultInfo].default_runfiles.files for d in deps], 14 | ) 15 | 16 | def transitive_deps(ctx, extra_files = [], extra_deps = []): 17 | """Calculates transitive sets of args. 18 | 19 | Calculates the transitive sets for ruby sources, data runfiles, 20 | include flags and runtime flags from the srcs, data and deps attributes 21 | in the context. 22 | Also adds extra_deps to the roots of the traversal. 23 | 24 | Args: 25 | ctx: a ctx object for a rb_library or a rb_binary rule. 26 | extra_files: a list of File objects to be added to the default_files 27 | extra_deps: a list of Target objects. 28 | """ 29 | deps = _transitive_srcs(ctx.attr.deps + extra_deps) 30 | files = depset(extra_files + ctx.files.srcs) 31 | default_files = ctx.runfiles( 32 | files = files.to_list(), 33 | transitive_files = depset(transitive = deps.default_files), 34 | collect_default = True, 35 | ) 36 | data_files = ctx.runfiles( 37 | files = ctx.files.data, 38 | transitive_files = depset(transitive = deps.data_files), 39 | collect_data = True, 40 | ) 41 | workspace = ctx.label.workspace_name or ctx.workspace_name 42 | includes = [ 43 | paths.join(workspace, inc) 44 | for inc in ctx.attr.includes 45 | ] 46 | return struct( 47 | srcs = depset( 48 | direct = ctx.files.srcs, 49 | transitive = deps.srcs, 50 | ), 51 | incpaths = depset( 52 | direct = includes, 53 | transitive = deps.incpaths, 54 | order = "topological", 55 | ), 56 | rubyopt = depset( 57 | direct = ctx.attr.rubyopt, 58 | transitive = deps.rubyopt, 59 | order = "topological", 60 | ), 61 | default_files = default_files, 62 | data_files = data_files, 63 | ) 64 | -------------------------------------------------------------------------------- /ruby/private/gem/gem.bzl: -------------------------------------------------------------------------------- 1 | load("//ruby/private:providers.bzl", "RubyGem") 2 | load("//ruby/private/tools:paths.bzl", "strip_short_path") 3 | 4 | # Runs gem with arbitrary arguments 5 | # eg: run_gem(runtime_ctx, ["install" "foo"]) 6 | def _rb_build_gem_impl(ctx): 7 | metadata_file = ctx.actions.declare_file("{}_build_metadata".format(ctx.attr.gem_name)) 8 | gemspec = ctx.attr.gemspec[RubyGem].gemspec 9 | 10 | _inputs = [ctx.file._gem_runner, metadata_file, gemspec] 11 | _srcs = [] 12 | for dep in ctx.attr.deps: 13 | file_deps = dep.files.to_list() 14 | _inputs.extend(file_deps) 15 | for f in file_deps: 16 | dest_path = strip_short_path(f.short_path, ctx.attr.strip_paths) 17 | _srcs.append({ 18 | "src_path": f.path, 19 | "dest_path": dest_path, 20 | }) 21 | 22 | ctx.actions.write( 23 | output = metadata_file, 24 | content = struct( 25 | srcs = _srcs, 26 | gemspec_path = gemspec.path, 27 | output_path = ctx.outputs.gem.path, 28 | source_date_epoch = ctx.attr.source_date_epoch, 29 | verbose = ctx.attr.verbose, 30 | ).to_json(), 31 | ) 32 | 33 | # the gem_runner does not support sandboxing because 34 | # gem build cannot handle symlinks and needs to write 35 | # the files as actual files. 36 | ctx.actions.run( 37 | inputs = _inputs, 38 | executable = ctx.attr.ruby_interpreter.files_to_run.executable, 39 | arguments = [ 40 | ctx.file._gem_runner.path, 41 | "--metadata", 42 | metadata_file.path, 43 | ], 44 | outputs = [ctx.outputs.gem], 45 | execution_requirements = { 46 | "no-sandbox": "1", 47 | }, 48 | ) 49 | 50 | _ATTRS = { 51 | "ruby_sdk": attr.string( 52 | default = "@org_ruby_lang_ruby_toolchain", 53 | ), 54 | "ruby_interpreter": attr.label( 55 | default = "@org_ruby_lang_ruby_toolchain//:ruby_bin", 56 | allow_files = True, 57 | executable = True, 58 | cfg = "host", 59 | ), 60 | "_gem_runner": attr.label( 61 | default = Label("@coinbase_rules_ruby//ruby/private/gem:gem_runner.rb"), 62 | allow_single_file = True, 63 | ), 64 | "gemspec": attr.label( 65 | # allow_files = True, 66 | ), 67 | "gem_name": attr.string( 68 | ), 69 | "deps": attr.label_list( 70 | allow_files = True, 71 | ), 72 | "version": attr.string(), 73 | "source_date_epoch": attr.string( 74 | doc = "Sets source_date_epoch env var which should make output gems hermetic", 75 | ), 76 | "strip_paths": attr.string_list(), 77 | "verbose": attr.bool(default = False), 78 | } 79 | 80 | rb_build_gem = rule( 81 | implementation = _rb_build_gem_impl, 82 | attrs = _ATTRS, 83 | outputs = { 84 | "gem": "%{gem_name}-%{version}.gem", 85 | }, 86 | ) 87 | -------------------------------------------------------------------------------- /ruby/private/gem/gemspec_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'optparse' 5 | 6 | def parse_opts 7 | output_file = nil 8 | metadata_file = nil 9 | template_file = nil 10 | 11 | OptionParser.new do |opts| 12 | opts.on('--template [ARG]', 'Gemspec template file') do |v| 13 | template_file = v 14 | end 15 | opts.on('--output [ARG]', 'Output file') do |v| 16 | output_file = v 17 | end 18 | opts.on('--metadata [ARG]', 'Metadata file') do |v| 19 | metadata_file = v 20 | end 21 | opts.on('-h', '--help') do |_v| 22 | puts opts 23 | exit 0 24 | end 25 | end.parse! 26 | 27 | [output_file, metadata_file, template_file] 28 | end 29 | 30 | def parse_metadata(metadata) 31 | # Expand all of the sources first 32 | metadata = expand_src_dirs(metadata) 33 | metadata = parse_require_paths(metadata) 34 | metadata = parse_gem_runtime_dependencies(metadata) 35 | metadata 36 | end 37 | 38 | def parse_require_paths(metadata) 39 | if metadata['require_paths'] == [] 40 | metadata['srcs'].each do |f| 41 | metadata['require_paths'] << File.dirname(f) if File.basename(f, '.rb') == metadata['name'] 42 | end 43 | end 44 | metadata 45 | end 46 | 47 | def _parse_gem_dependency(gem) 48 | 's.add_runtime_dependency ' + gem 49 | end 50 | 51 | def parse_gem_runtime_dependencies(metadata) 52 | dependency_list = [] 53 | if metadata['gem_runtime_dependencies'] != [] 54 | metadata['gem_runtime_dependencies'].each do |gem| 55 | dependency_list.append(_parse_gem_dependency(gem)) 56 | end 57 | end 58 | metadata['gem_runtime_dependencies'] = dependency_list.join("\n ") 59 | metadata 60 | end 61 | 62 | def expand_src_dirs(metadata) 63 | # Files and required paths can include a directory which gemspec 64 | # cannot handle. This will convert directories to individual files 65 | srcs = metadata['raw_srcs'] 66 | new_srcs = [] 67 | srcs.each do |src| 68 | src_path = src['src_path'] 69 | dest_path = src['dest_path'] 70 | if File.directory?(src_path) 71 | Dir.glob("#{src_path}/**/*") do |f| 72 | # expand the directory, replacing each src path with its dest path 73 | new_srcs << f.gsub(src_path, dest_path) if File.file?(f) 74 | end 75 | elsif File.file?(src_path) 76 | new_srcs << dest_path 77 | end 78 | end 79 | metadata['srcs'] = new_srcs 80 | metadata 81 | end 82 | 83 | def main 84 | output_file, metadata_file, template_file = parse_opts 85 | data = File.read(template_file) 86 | m = File.read(metadata_file) 87 | metadata = JSON.parse(m) 88 | 89 | metadata = parse_metadata(metadata) 90 | filtered_data = data 91 | 92 | metadata.each do |key, value| 93 | replace_val = "{#{key}}" 94 | filtered_data = filtered_data.gsub(replace_val, value.to_s) 95 | end 96 | 97 | File.open(output_file, 'w') do |out_file| 98 | out_file.write(filtered_data) 99 | end 100 | end 101 | 102 | main if $PROGRAM_NAME == __FILE__ 103 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | base-executor: 5 | resource_class: medium 6 | docker: 7 | - image: circleci/ruby:2.6.5 8 | environment: 9 | PATH: "/usr/local/bin:/usr/bin:/sbin:/opt/bin:/home/circleci/repo/bin:/bin:/sbin:/usr/sbin" 10 | BUNDLE_PATH: /home/circleci/.bundle_cache 11 | 12 | commands: 13 | setup-bazel-env: 14 | description: "Setup jobs for running bazel" 15 | steps: 16 | - run: 17 | name: "Setup bazelrc" 18 | command: | 19 | cp ~/project/.circleci/.bazelrc ~/.bazelrc 20 | - run: 21 | name: "Install bazelisk" 22 | command: | 23 | sudo curl -L -o /usr/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v1.0/bazelisk-linux-amd64 \ 24 | && sudo chmod +x /usr/bin/bazel 25 | 26 | bazel-build-test-all: 27 | description: "Bazel build and test all" 28 | steps: 29 | - run: 30 | name: "Bazel Build & Test" 31 | command: | 32 | bazel build //...:all \ 33 | && bazel test //...:all \ 34 | 35 | jobs: 36 | rubocop: 37 | executor: base-executor 38 | steps: 39 | - checkout 40 | 41 | - run: 42 | name: Install Bundler 43 | command: | 44 | gem install bundler:2.0.2 --no-doc 45 | bundle install --jobs=4 --retry=3 --path ${BUNDLE_PATH} 46 | 47 | - run: 48 | name: "Rubocop Style Check" 49 | command: bundle exec rubocop -E -P 50 | 51 | bazel_build_workspace: 52 | executor: base-executor 53 | steps: 54 | - checkout 55 | - setup-bazel-env 56 | - bazel-build-test-all 57 | 58 | bazel_build_examples_simple_script: 59 | executor: base-executor 60 | working_directory: ~/project/examples/simple_script 61 | steps: 62 | - checkout: 63 | path: ~/project 64 | - setup-bazel-env 65 | - bazel-build-test-all 66 | 67 | - run: 68 | name: "Simple Script: Run bin and rubocop" 69 | command: | 70 | bazel run :bin \ 71 | && bazel run :rubocop 72 | 73 | bazel_build_examples_gem: 74 | executor: base-executor 75 | working_directory: ~/project/examples/example_gem 76 | steps: 77 | - checkout: 78 | path: ~/project 79 | - setup-bazel-env 80 | - run: 81 | name: "Build all" 82 | command: bazel build //...:all 83 | 84 | bazel_test_integration_test_workspace: 85 | executor: base-executor 86 | working_directory: ~/project/ruby/tests/integration 87 | steps: 88 | - checkout: 89 | path: ~/project 90 | - setup-bazel-env 91 | - bazel-build-test-all 92 | 93 | 94 | buildifier: 95 | executor: base-executor 96 | 97 | steps: 98 | - checkout 99 | - setup-bazel-env 100 | 101 | - run: 102 | name: "Bazel Buildifier Run" 103 | command: | 104 | bazel run :buildifier-check 105 | 106 | workflows: 107 | rules_ruby: 108 | jobs: 109 | - bazel_build_workspace 110 | - bazel_build_examples_simple_script 111 | - bazel_build_examples_gem 112 | - bazel_test_integration_test_workspace 113 | - buildifier 114 | - rubocop 115 | -------------------------------------------------------------------------------- /ruby/private/gem/gem_runner.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'fileutils' 4 | require 'json' 5 | require 'optparse' 6 | require 'rubygems' 7 | require 'rubygems/gem_runner' 8 | require 'rubygems/exceptions' 9 | require 'rubygems/version' 10 | require 'tmpdir' 11 | 12 | def check_rubygems_version 13 | required_version = Gem::Requirement.new '>= 1.8.7' 14 | 15 | abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}" unless required_version.satisfied_by? Gem.ruby_version 16 | end 17 | 18 | def parse_opts 19 | metadata_file = nil 20 | 21 | OptionParser.new do |opts| 22 | opts.on('--metadata [ARG]', 'Metadata file') do |v| 23 | metadata_file = v 24 | end 25 | opts.on('-h', '--help') do |_v| 26 | puts opts 27 | exit 0 28 | end 29 | end.parse! 30 | 31 | metadata_file 32 | end 33 | 34 | def copy_srcs(dir, srcs, verbose) 35 | # Sources need to be moved from their bazel_out locations 36 | # to the correct folder in the ruby gem. 37 | srcs.each do |src| 38 | src_path = src['src_path'] 39 | dest_path = src['dest_path'] 40 | tmpname = File.join(dir, File.dirname(dest_path)) 41 | FileUtils.mkdir_p(tmpname) 42 | puts "copying #{src_path} to #{tmpname}" if verbose 43 | FileUtils.cp_r(src_path, tmpname) 44 | # Copying a directory will not dereference symlinks 45 | # in the directory. They need to be removed too. 46 | dereference_symlinks(tmpname, verbose) if File.directory?(tmpname) 47 | end 48 | end 49 | 50 | def dereference_symlinks(dir, verbose) 51 | Dir.glob("#{dir}/**/*") do |src| 52 | if File.symlink?(src) 53 | actual_src = File.realpath(src) 54 | puts "Dereferencing symlink at #{src} to #{actual_src}" if verbose 55 | FileUtils.safe_unlink(src) 56 | FileUtils.cp_r(actual_src, src) 57 | end 58 | end 59 | end 60 | 61 | def copy_gemspec(dir, gemspec_path) 62 | # The gemspec file needs to be in the root of the build dir 63 | FileUtils.cp(gemspec_path, dir) 64 | end 65 | 66 | def do_build(dir, gemspec_path, output_path) 67 | args = [ 68 | 'build', 69 | File.join(dir, File.basename(gemspec_path)) 70 | ] 71 | # Older versions of rubygems work better if the 72 | # cwd is the root of the gem dir. 73 | Dir.chdir(dir) do 74 | Gem::GemRunner.new.run args 75 | end 76 | FileUtils.cp(File.join(dir, File.basename(output_path)), output_path) 77 | end 78 | 79 | def build_gem(metadata) 80 | # We copy all related files to a tmpdir, build the entire gem in that tmpdir 81 | # and then copy the output gem into the correct bazel output location. 82 | verbose = metadata['verbose'] 83 | Dir.mktmpdir do |dir| 84 | copy_srcs(dir, metadata['srcs'], verbose) 85 | copy_gemspec(dir, metadata['gemspec_path']) 86 | do_build(dir, metadata['gemspec_path'], metadata['output_path']) 87 | end 88 | end 89 | 90 | def main 91 | check_rubygems_version 92 | metadata_file = parse_opts 93 | m = File.read(metadata_file) 94 | metadata = JSON.parse(m) 95 | 96 | if metadata['source_date_epoch'] != '' 97 | # I think this will make it hermetic! YAY! 98 | ENV['SOURCE_DATE_EPOCH'] = metadata['source_date_epoch'] 99 | end 100 | 101 | begin 102 | build_gem(metadata) 103 | rescue Gem::SystemExitException => e 104 | exit e.exit_code 105 | end 106 | end 107 | 108 | main if $PROGRAM_NAME == __FILE__ 109 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "coinbase_rules_ruby") 2 | 3 | load("@//ruby:deps.bzl", "rules_ruby_dependencies") 4 | 5 | rules_ruby_dependencies() 6 | 7 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 8 | 9 | bazel_skylib_workspace() 10 | 11 | load("@bazel_skylib//lib:versions.bzl", "versions") 12 | 13 | versions.check("1.2.1") 14 | 15 | load("@//ruby:deps.bzl", "ruby_register_toolchains") 16 | 17 | ruby_register_toolchains() 18 | 19 | local_repository( 20 | name = "coinbase_rules_ruby_ruby_tests_testdata_another_workspace", 21 | path = "ruby/tests/testdata/another_workspace", 22 | ) 23 | 24 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 25 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 26 | 27 | # installing go for buildifier 28 | http_archive( 29 | name = "io_bazel_rules_go", 30 | sha256 = "842ec0e6b4fbfdd3de6150b61af92901eeb73681fd4d185746644c338f51d4c0", 31 | urls = [ 32 | "https://storage.googleapis.com/bazel-mirror/github.com/bazelbuild/rules_go/releases/download/v0.20.1/rules_go-v0.20.1.tar.gz", 33 | "https://github.com/bazelbuild/rules_go/releases/download/v0.20.1/rules_go-v0.20.1.tar.gz", 34 | ], 35 | ) 36 | 37 | git_repository( 38 | name = "bazel_gazelle", 39 | commit = "11a9ed24876401ee8b570c04de3a132e62415304", 40 | remote = "https://github.com/bazelbuild/bazel-gazelle", 41 | ) 42 | 43 | git_repository( 44 | name = "com_google_protobuf", 45 | commit = "09745575a923640154bcf307fba8aedff47f240a", 46 | remote = "https://github.com/protocolbuffers/protobuf", 47 | shallow_since = "1558721209 -0700", 48 | ) 49 | 50 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 51 | 52 | go_rules_dependencies() 53 | 54 | go_register_toolchains(go_version = "1.12.9") 55 | 56 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 57 | 58 | gazelle_dependencies() 59 | 60 | load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") 61 | 62 | protobuf_deps() 63 | 64 | # rules_docker 65 | 66 | http_archive( 67 | name = "io_bazel_rules_docker", 68 | sha256 = "14ac30773fdb393ddec90e158c9ec7ebb3f8a4fd533ec2abbfd8789ad81a284b", 69 | strip_prefix = "rules_docker-0.12.1", 70 | urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.12.1/rules_docker-v0.12.1.tar.gz"], 71 | ) 72 | 73 | load( 74 | "@io_bazel_rules_docker//repositories:repositories.bzl", 75 | container_repositories = "repositories", 76 | ) 77 | 78 | container_repositories() 79 | 80 | load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") 81 | 82 | container_deps() 83 | 84 | load( 85 | "@io_bazel_rules_docker//container:container.bzl", 86 | "container_pull", 87 | ) 88 | 89 | container_pull( 90 | name = "ruby_base_container", 91 | digest = "sha256:da560e130d6a4b75b099e932a98331ec3b2420b914d51a88edc4fe3c60aee9b1", # alpine linux/amd64 92 | registry = "docker.io", 93 | repository = "library/ruby", 94 | ) 95 | 96 | load("@coinbase_rules_ruby//ruby:defs.bzl", "bundle_install") 97 | 98 | bundle_install( 99 | name = "bundle", 100 | bundler_version = "2.1.2", 101 | excludes = { 102 | "mini_portile": ["test/**/*"], 103 | }, 104 | gemfile = "//:Gemfile", 105 | gemfile_lock = "//:Gemfile.lock", 106 | ) 107 | 108 | load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies") 109 | 110 | rules_pkg_dependencies() 111 | 112 | git_repository( 113 | name = "wspace_bazel_skylib", 114 | commit = "9935e0f820692f5f38e3b00c64ccbbff30cebe11", 115 | remote = "https://github.com/bazelbuild/bazel-skylib", 116 | ) 117 | 118 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 119 | 120 | bazel_skylib_workspace() 121 | -------------------------------------------------------------------------------- /ruby/private/binary/binary.bzl: -------------------------------------------------------------------------------- 1 | load("//ruby/private:constants.bzl", "RUBY_ATTRS", "TOOLCHAIN_TYPE_NAME") 2 | load( 3 | "//ruby/private/tools:deps.bzl", 4 | _transitive_deps = "transitive_deps", 5 | ) 6 | 7 | def _to_manifest_path(ctx, file): 8 | if file.short_path.startswith("../"): 9 | return file.short_path[3:] 10 | else: 11 | return ("%s/%s" % (ctx.workspace_name, file.short_path)) 12 | 13 | def _get_gem_path(incpaths): 14 | """ 15 | incpaths is a list of `/lib/ruby//gems/-/lib` 16 | The gem_path is `/lib/ruby/` so we can go from an incpath to the 17 | gem_path pretty easily without much additional work. 18 | """ 19 | if len(incpaths) == 0: 20 | return "" 21 | for incpath in incpaths: 22 | if "/lib/ruby/" in incpath and "/gems/" in incpath: 23 | return incpath.rsplit("/", 3)[0] 24 | 25 | # if none match the lib ruby setup then we will default to the first one 26 | return incpaths[0].rsplit("/", 3)[0] 27 | 28 | def _get_bundle_path(gem_path): 29 | """ 30 | This is mainly a way to get the bundle name so we can add the path to bundler to the gem 31 | path env var. The bundle path is just: `/bundler` 32 | """ 33 | if not gem_path: 34 | return "" 35 | return gem_path.split("/")[0] + "/bundler" 36 | 37 | # Having this function allows us to override otherwise frozen attributes 38 | # such as main, srcs and deps. We use this in rb_rspec_test rule by 39 | # adding rspec as a main, and sources, and rspec gem as a dependency. 40 | # 41 | # There could be similar situations in the future where we might want 42 | # to create a rule (eg, rubocop) that does exactly the same. 43 | def rb_binary_macro(ctx, main, srcs): 44 | sdk = ctx.toolchains[TOOLCHAIN_TYPE_NAME].ruby_runtime 45 | interpreter = sdk.interpreter[DefaultInfo].files_to_run.executable 46 | 47 | if not main: 48 | expected_name = "%s.rb" % ctx.attr.name 49 | for f in srcs: 50 | if f.label.name == expected_name: 51 | main = f.files.to_list()[0] 52 | break 53 | if not main: 54 | fail( 55 | ("main must be present unless the name of the rule matches to " + 56 | "one of the srcs"), 57 | "main", 58 | ) 59 | 60 | executable = ctx.actions.declare_file(ctx.attr.name) 61 | 62 | deps = _transitive_deps( 63 | ctx, 64 | extra_files = [executable], 65 | extra_deps = ctx.attr._misc_deps, 66 | ) 67 | 68 | gem_path = _get_gem_path(deps.incpaths.to_list()) 69 | bundle_path = _get_bundle_path(gem_path) 70 | 71 | gems_to_pristine = ctx.attr.force_gem_pristine 72 | 73 | rubyopt = reversed(deps.rubyopt.to_list()) 74 | 75 | ctx.actions.expand_template( 76 | template = ctx.file._wrapper_template, 77 | output = executable, 78 | substitutions = { 79 | "{loadpaths}": repr(deps.incpaths.to_list()), 80 | "{rubyopt}": repr(rubyopt), 81 | "{main}": repr(_to_manifest_path(ctx, main)), 82 | "{interpreter}": _to_manifest_path(ctx, interpreter), 83 | "{gem_path}": gem_path, 84 | "{bundle_path}": bundle_path, 85 | "{should_gem_pristine}": str(len(gems_to_pristine) > 0).lower(), 86 | "{gems_to_pristine}": " ".join(gems_to_pristine), 87 | "{run_under}": ctx.attr.run_under, 88 | }, 89 | ) 90 | 91 | info = DefaultInfo( 92 | executable = executable, 93 | runfiles = deps.default_files.merge(deps.data_files), 94 | ) 95 | 96 | return [info] 97 | 98 | def rb_binary_impl(ctx): 99 | return rb_binary_macro( 100 | ctx, 101 | ctx.file.main, 102 | ctx.attr.srcs, 103 | ) 104 | 105 | rb_binary = rule( 106 | implementation = rb_binary_impl, 107 | attrs = RUBY_ATTRS, 108 | executable = True, 109 | toolchains = [TOOLCHAIN_TYPE_NAME], 110 | ) 111 | 112 | rb_test = rule( 113 | implementation = rb_binary_impl, 114 | attrs = RUBY_ATTRS, 115 | test = True, 116 | toolchains = [TOOLCHAIN_TYPE_NAME], 117 | ) 118 | -------------------------------------------------------------------------------- /ruby/private/gem/gemspec.bzl: -------------------------------------------------------------------------------- 1 | load( 2 | "//ruby/private/tools:deps.bzl", 3 | _transitive_deps = "transitive_deps", 4 | ) 5 | load("//ruby/private/tools:paths.bzl", "strip_short_path") 6 | load( 7 | "//ruby/private:providers.bzl", 8 | "RubyGem", 9 | "RubyLibrary", 10 | ) 11 | 12 | def _get_transitive_srcs(srcs, deps): 13 | return depset( 14 | srcs, 15 | transitive = [dep[RubyLibrary].transitive_ruby_srcs for dep in deps], 16 | ) 17 | 18 | def _rb_gem_impl(ctx): 19 | gemspec = ctx.actions.declare_file("{}.gemspec".format(ctx.attr.gem_name)) 20 | metadata_file = ctx.actions.declare_file("{}_metadata".format(ctx.attr.gem_name)) 21 | 22 | _ruby_files = [] 23 | file_deps = _get_transitive_srcs([], ctx.attr.deps).to_list() 24 | for f in file_deps: 25 | # For some files the src_path and dest_path will be the same, but 26 | # for othrs the src_path will be in bazel)out while the dest_path 27 | # will be from the workspace root. 28 | dest_path = strip_short_path(f.short_path, ctx.attr.strip_paths) 29 | _ruby_files.append({ 30 | "src_path": f.path, 31 | "dest_path": dest_path, 32 | }) 33 | 34 | ctx.actions.write( 35 | output = metadata_file, 36 | content = struct( 37 | name = ctx.attr.gem_name, 38 | raw_srcs = _ruby_files, 39 | authors = ctx.attr.authors, 40 | version = ctx.attr.version, 41 | licenses = ctx.attr.licenses, 42 | require_paths = ctx.attr.require_paths, 43 | gem_runtime_dependencies = ctx.attr.gem_runtime_dependencies, 44 | ).to_json(), 45 | ) 46 | 47 | ctx.actions.run( 48 | inputs = [ 49 | ctx.file._gemspec_template, 50 | ctx.file._gemspec_builder, 51 | metadata_file, 52 | ] + file_deps, 53 | executable = ctx.attr.ruby_interpreter.files_to_run.executable, 54 | arguments = [ 55 | ctx.file._gemspec_builder.path, 56 | "--output", 57 | gemspec.path, 58 | "--metadata", 59 | metadata_file.path, 60 | "--template", 61 | ctx.file._gemspec_template.path, 62 | ], 63 | outputs = [gemspec], 64 | execution_requirements = { 65 | "no-sandbox": "1", 66 | }, 67 | ) 68 | 69 | return [ 70 | DefaultInfo(files = _get_transitive_srcs([gemspec], ctx.attr.deps)), 71 | RubyGem( 72 | ctx = ctx, 73 | version = ctx.attr.version, 74 | gemspec = gemspec, 75 | ), 76 | ] 77 | 78 | _ATTRS = { 79 | "version": attr.string( 80 | default = "0.0.1", 81 | ), 82 | "authors": attr.string_list(), 83 | "licenses": attr.string_list(), 84 | "deps": attr.label_list( 85 | allow_files = True, 86 | ), 87 | "data": attr.label_list( 88 | allow_files = True, 89 | ), 90 | "gem_name": attr.string(), 91 | "srcs": attr.label_list( 92 | allow_files = True, 93 | default = [], 94 | ), 95 | "gem_deps": attr.label_list( 96 | allow_files = True, 97 | ), 98 | "gem_runtime_dependencies": attr.string_list( 99 | mandatory = False, 100 | allow_empty = True, 101 | default = [], 102 | ), 103 | "require_paths": attr.string_list(), 104 | "strip_paths": attr.string_list(), 105 | "_gemspec_template": attr.label( 106 | allow_single_file = True, 107 | default = "gemspec_template.tpl", 108 | ), 109 | "ruby_sdk": attr.string( 110 | default = "@org_ruby_lang_ruby_toolchain", 111 | ), 112 | "ruby_interpreter": attr.label( 113 | default = "@org_ruby_lang_ruby_toolchain//:ruby_bin", 114 | allow_files = True, 115 | executable = True, 116 | cfg = "host", 117 | ), 118 | "_gemspec_builder": attr.label( 119 | default = Label("@coinbase_rules_ruby//ruby/private/gem:gemspec_builder.rb"), 120 | allow_single_file = True, 121 | ), 122 | } 123 | 124 | rb_gemspec = rule( 125 | implementation = _rb_gem_impl, 126 | attrs = _ATTRS, 127 | provides = [DefaultInfo, RubyGem], 128 | ) 129 | -------------------------------------------------------------------------------- /examples/simple_rails_api/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.4) 5 | actionpack (= 6.0.3.4) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.4) 9 | actionpack (= 6.0.3.4) 10 | activejob (= 6.0.3.4) 11 | activerecord (= 6.0.3.4) 12 | activestorage (= 6.0.3.4) 13 | activesupport (= 6.0.3.4) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.4) 16 | actionpack (= 6.0.3.4) 17 | actionview (= 6.0.3.4) 18 | activejob (= 6.0.3.4) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.4) 22 | actionview (= 6.0.3.4) 23 | activesupport (= 6.0.3.4) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.4) 29 | actionpack (= 6.0.3.4) 30 | activerecord (= 6.0.3.4) 31 | activestorage (= 6.0.3.4) 32 | activesupport (= 6.0.3.4) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.4) 35 | activesupport (= 6.0.3.4) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.4) 41 | activesupport (= 6.0.3.4) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.4) 44 | activesupport (= 6.0.3.4) 45 | activerecord (6.0.3.4) 46 | activemodel (= 6.0.3.4) 47 | activesupport (= 6.0.3.4) 48 | activestorage (6.0.3.4) 49 | actionpack (= 6.0.3.4) 50 | activejob (= 6.0.3.4) 51 | activerecord (= 6.0.3.4) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.4) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | bootsnap (1.4.8) 60 | msgpack (~> 1.0) 61 | builder (3.2.4) 62 | byebug (11.1.3) 63 | coderay (1.1.3) 64 | concurrent-ruby (1.1.7) 65 | crass (1.0.6) 66 | erubi (1.9.0) 67 | ffi (1.13.1) 68 | globalid (0.4.2) 69 | activesupport (>= 4.2.0) 70 | i18n (1.8.5) 71 | concurrent-ruby (~> 1.0) 72 | listen (3.1.5) 73 | rb-fsevent (~> 0.9, >= 0.9.4) 74 | rb-inotify (~> 0.9, >= 0.9.7) 75 | ruby_dep (~> 1.2) 76 | loofah (2.7.0) 77 | crass (~> 1.0.2) 78 | nokogiri (>= 1.5.9) 79 | mail (2.7.1) 80 | mini_mime (>= 0.1.1) 81 | marcel (0.3.3) 82 | mimemagic (~> 0.3.2) 83 | method_source (1.0.0) 84 | mimemagic (0.3.5) 85 | mini_mime (1.0.2) 86 | mini_portile2 (2.5.0) 87 | minitest (5.14.2) 88 | msgpack (1.3.3) 89 | nio4r (2.5.4) 90 | nokogiri (1.11.1) 91 | mini_portile2 (~> 2.5.0) 92 | racc (~> 1.4) 93 | pry (0.13.1) 94 | coderay (~> 1.1) 95 | method_source (~> 1.0) 96 | pry-byebug (3.9.0) 97 | byebug (~> 11.0) 98 | pry (~> 0.13.0) 99 | puma (4.3.6) 100 | nio4r (~> 2.0) 101 | racc (1.5.2) 102 | rack (2.2.3) 103 | rack-test (1.1.0) 104 | rack (>= 1.0, < 3) 105 | rails (6.0.3.4) 106 | actioncable (= 6.0.3.4) 107 | actionmailbox (= 6.0.3.4) 108 | actionmailer (= 6.0.3.4) 109 | actionpack (= 6.0.3.4) 110 | actiontext (= 6.0.3.4) 111 | actionview (= 6.0.3.4) 112 | activejob (= 6.0.3.4) 113 | activemodel (= 6.0.3.4) 114 | activerecord (= 6.0.3.4) 115 | activestorage (= 6.0.3.4) 116 | activesupport (= 6.0.3.4) 117 | bundler (>= 1.3.0) 118 | railties (= 6.0.3.4) 119 | sprockets-rails (>= 2.0.0) 120 | rails-dom-testing (2.0.3) 121 | activesupport (>= 4.2.0) 122 | nokogiri (>= 1.6) 123 | rails-html-sanitizer (1.3.0) 124 | loofah (~> 2.3) 125 | railties (6.0.3.4) 126 | actionpack (= 6.0.3.4) 127 | activesupport (= 6.0.3.4) 128 | method_source 129 | rake (>= 0.8.7) 130 | thor (>= 0.20.3, < 2.0) 131 | rake (13.0.1) 132 | rb-fsevent (0.10.4) 133 | rb-inotify (0.10.1) 134 | ffi (~> 1.0) 135 | ruby_dep (1.5.0) 136 | spring (2.1.1) 137 | spring-watcher-listen (2.0.1) 138 | listen (>= 2.7, < 4.0) 139 | spring (>= 1.2, < 3.0) 140 | sprockets (4.0.2) 141 | concurrent-ruby (~> 1.0) 142 | rack (> 1, < 3) 143 | sprockets-rails (3.2.2) 144 | actionpack (>= 4.0) 145 | activesupport (>= 4.0) 146 | sprockets (>= 3.0.0) 147 | sqlite3 (1.4.2) 148 | thor (1.0.1) 149 | thread_safe (0.3.6) 150 | tzinfo (1.2.7) 151 | thread_safe (~> 0.1) 152 | websocket-driver (0.7.3) 153 | websocket-extensions (>= 0.1.0) 154 | websocket-extensions (0.1.5) 155 | zeitwerk (2.4.0) 156 | 157 | PLATFORMS 158 | ruby 159 | 160 | DEPENDENCIES 161 | actionpack (>= 6.0.3.3) 162 | actionview (>= 6.0.3.3) 163 | bootsnap (>= 1.4.2) 164 | byebug 165 | listen (>= 3.0.5, < 3.2) 166 | pry-byebug 167 | puma (~> 4.3) 168 | rails (~> 6.0.2) 169 | spring 170 | spring-watcher-listen (~> 2.0.0) 171 | sqlite3 (~> 1.4) 172 | tzinfo-data 173 | 174 | RUBY VERSION 175 | ruby 2.6.5p114 176 | 177 | BUNDLED WITH 178 | 2.1.4 179 | -------------------------------------------------------------------------------- /ruby/private/binary/binary_wrapper.tpl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Ruby-port of the Bazel's wrapper script for Python 4 | 5 | # Copyright 2017 The Bazel Authors. All rights reserved. 6 | # Copyright 2019 BazelRuby Authors. All rights reserved. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | require 'rbconfig' 22 | 23 | def find_runfiles 24 | stub_filename = File.absolute_path($0) 25 | runfiles = "#{stub_filename}.runfiles" 26 | loop do 27 | case 28 | when File.directory?(runfiles) 29 | return runfiles 30 | when %r!(.*\.runfiles)/.*!o =~ stub_filename 31 | return $1 32 | when File.symlink?(stub_filename) 33 | target = File.readlink(stub_filename) 34 | stub_filename = File.absolute_path(target, File.dirname(stub_filename)) 35 | else 36 | break 37 | end 38 | end 39 | raise "Cannot find .runfiles directory for #{$0}" 40 | end 41 | 42 | def create_loadpath_entries(custom, runfiles) 43 | [runfiles] + custom.map {|path| File.join(runfiles, path) } 44 | end 45 | 46 | def get_repository_imports(runfiles) 47 | Dir.children(runfiles).map {|d| 48 | File.join(runfiles, d) 49 | }.select {|d| 50 | File.directory? d 51 | } 52 | end 53 | 54 | # Finds the runfiles manifest or the runfiles directory. 55 | def runfiles_envvar(runfiles) 56 | # If this binary is the data-dependency of another one, the other sets 57 | # RUNFILES_MANIFEST_FILE or RUNFILES_DIR for our sake. 58 | manifest = ENV['RUNFILES_MANIFEST_FILE'] 59 | if manifest 60 | return ['RUNFILES_MANIFEST_FILE', manifest] 61 | end 62 | 63 | dir = ENV['RUNFILES_DIR'] 64 | if dir 65 | return ['RUNFILES_DIR', dir] 66 | end 67 | 68 | # Look for the runfiles "output" manifest, argv[0] + ".runfiles_manifest" 69 | manifest = runfiles + '_manifest' 70 | if File.exists?(manifest) 71 | return ['RUNFILES_MANIFEST_FILE', manifest] 72 | end 73 | 74 | # Look for the runfiles "input" manifest, argv[0] + ".runfiles/MANIFEST" 75 | manifest = File.join(runfiles, 'MANIFEST') 76 | if File.exists?(manifest) 77 | return ['RUNFILES_DIR', manifest] 78 | end 79 | 80 | # If running in a sandbox and no environment variables are set, then 81 | # Look for the runfiles next to the binary. 82 | if runfiles.end_with?('.runfiles') and File.directory?(runfiles) 83 | return ['RUNFILES_DIR', runfiles] 84 | end 85 | end 86 | 87 | def find_rb_binary 88 | File.join( 89 | RbConfig::CONFIG['bindir'], 90 | RbConfig::CONFIG['ruby_install_name'], 91 | ) 92 | end 93 | 94 | def find_gem_binary 95 | File.join( 96 | RbConfig::CONFIG['bindir'], 97 | 'gem', 98 | ) 99 | end 100 | 101 | def setup_gem_path(runfiles) 102 | # Bundle path is based on gem_path so cannot be "" if gem_path exists 103 | if "{gem_path}" == "" 104 | return 105 | end 106 | full_bundle_path = File.join(runfiles, "{bundle_path}") 107 | full_gem_path = File.join(runfiles, "{gem_path}") 108 | 109 | # Caution: The str replace in bazel is also based on {var} so may conflict with 110 | # ruby str replacement. 111 | ENV["GEM_PATH"] = "#{full_gem_path}:#{full_bundle_path}" 112 | ENV["GEM_HOME"] = "#{full_gem_path}" 113 | end 114 | 115 | def main(args) 116 | runfiles = find_runfiles 117 | 118 | runfiles_envkey, runfiles_envvalue = runfiles_envvar(runfiles) 119 | ENV[runfiles_envkey] = runfiles_envvalue if runfiles_envkey 120 | 121 | custom_loadpaths = {loadpaths} 122 | loadpaths = create_loadpath_entries(custom_loadpaths, runfiles) 123 | loadpaths += get_repository_imports(runfiles) 124 | loadpaths += ENV['RUBYLIB'].split(':') if ENV.key?('RUBYLIB') 125 | ENV['RUBYLIB'] = loadpaths.join(':') 126 | 127 | setup_gem_path(runfiles) 128 | 129 | ruby_program = find_rb_binary 130 | 131 | main = {main} 132 | main = File.join(runfiles, main) 133 | rubyopt = {rubyopt}.map do |opt| 134 | opt.gsub(/\${(.+?)}/o) do 135 | case $1 136 | when 'RUNFILES_DIR' 137 | runfiles 138 | else 139 | ENV[$1] 140 | end 141 | end 142 | end 143 | 144 | # This is a jank hack because some of our gems are having issues with how 145 | # they are being installed. Most gems are fine, but this fixes the ones that 146 | # aren't. Put it here instead of in the library because we want to fix the 147 | # underlying issue and then tear this out. 148 | if {should_gem_pristine} then 149 | gem_program = find_gem_binary 150 | puts "Running pristine on {gems_to_pristine}" 151 | system(gem_program + " pristine {gems_to_pristine}") 152 | end 153 | if "{run_under}" != "" then 154 | Dir.chdir("{run_under}") 155 | end 156 | 157 | exec(ruby_program, *rubyopt, main, *args) 158 | end 159 | 160 | if __FILE__ == $0 161 | main(ARGV) 162 | end 163 | -------------------------------------------------------------------------------- /ruby/private/toolchains/ruby_runtime.bzl: -------------------------------------------------------------------------------- 1 | load("//ruby/private:constants.bzl", "RULES_RUBY_WORKSPACE_NAME") 2 | load("//ruby/private/toolchains:repository_context.bzl", "ruby_repository_context") 3 | 4 | def _is_subpath(path, ancestors): 5 | """Determines if path is a subdirectory of one of the ancestors""" 6 | for ancestor in ancestors: 7 | if not ancestor.endswith("/"): 8 | ancestor += "/" 9 | if path.startswith(ancestor): 10 | return True 11 | return False 12 | 13 | def _relativate(path): 14 | if not path: 15 | return path 16 | 17 | # Assuming that absolute paths start with "/". 18 | # TODO(yugui) support windows 19 | if path.startswith("/"): 20 | return path[1:] 21 | else: 22 | return path 23 | 24 | def _list_libdirs(ruby): 25 | """List the LOAD_PATH of the ruby""" 26 | paths = ruby.eval(ruby, 'print $:.join("\\n")') 27 | paths = sorted(paths.split("\n")) 28 | rel_paths = [_relativate(path) for path in paths] 29 | return (paths, rel_paths) 30 | 31 | def _install_dirs(ctx, ruby, *names): 32 | paths = sorted([ruby.rbconfig(ruby, name) for name in names]) 33 | rel_paths = [_relativate(path) for path in paths] 34 | for i, (path, rel_path) in enumerate(zip(paths, rel_paths)): 35 | if not _is_subpath(path, paths[:i]): 36 | ctx.symlink(path, rel_path) 37 | return rel_paths 38 | 39 | def _install_ruby(ctx, ruby): 40 | # Places SDK 41 | ctx.symlink(ruby.interpreter_realpath, ruby.rel_interpreter_path) 42 | 43 | # Places the interpreter at a predictable place regardless of the actual binary name 44 | # so that bundle_install can depend on it. 45 | ctx.template( 46 | "ruby", 47 | ctx.attr._interpreter_wrapper_template, 48 | substitutions = { 49 | "{workspace_name}": ctx.name, 50 | "{rel_interpreter_path}": ruby.rel_interpreter_path, 51 | }, 52 | ) 53 | 54 | # Install libruby 55 | static_library = ruby.expand_rbconfig(ruby, "${libdir}/${LIBRUBY_A}") 56 | if ctx.path(static_library).exists: 57 | ctx.symlink(static_library, _relativate(static_library)) 58 | else: 59 | static_library = None 60 | 61 | shared_library = ruby.expand_rbconfig(ruby, "${libdir}/${LIBRUBY_SO}") 62 | if ctx.path(shared_library).exists: 63 | ctx.symlink(shared_library, _relativate(shared_library)) 64 | else: 65 | shared_library = None 66 | 67 | return struct( 68 | includedirs = _install_dirs(ctx, ruby, "rubyarchhdrdir", "rubyhdrdir"), 69 | static_library = _relativate(static_library), 70 | shared_library = _relativate(shared_library), 71 | ) 72 | 73 | def host_ruby_is_correct_version(ctx, version): 74 | interpreter_path = ctx.which("ruby") 75 | 76 | if not interpreter_path: 77 | print("Can't find ruby interpreter in the PATH") 78 | return False 79 | 80 | ruby_version = ctx.execute(["ruby", "-e", "print RUBY_VERSION"]).stdout 81 | 82 | have_ruby_version = ruby_version.startswith(version) 83 | 84 | if have_ruby_version: 85 | print("Found local Ruby SDK version '%s' which matches requested version '%s'" % (ruby_version, version)) 86 | 87 | return have_ruby_version 88 | 89 | def _ruby_runtime_impl(ctx): 90 | # If the current version of ruby is correct use that 91 | version = ctx.attr.version 92 | if version == "host": 93 | interpreter_path = ctx.which("ruby") 94 | else: 95 | if not host_ruby_is_correct_version(ctx, version): 96 | fail("\n\nIncorrect ruby version found: required version " + version + ".\nInstall with rbenv then `rbenv global \n\n") 97 | interpreter_path = ctx.which("ruby") 98 | 99 | if not interpreter_path: 100 | fail( 101 | "Command 'ruby' not found. Set $PATH or specify interpreter_path", 102 | "interpreter_path", 103 | ) 104 | 105 | ruby = ruby_repository_context(ctx, interpreter_path) 106 | 107 | installed = _install_ruby(ctx, ruby) 108 | 109 | ctx.template( 110 | "BUILD.bazel", 111 | ctx.attr._buildfile_template, 112 | substitutions = { 113 | "{includes}": repr(installed.includedirs), 114 | "{hdrs}": repr(["%s/**/*.h" % path for path in installed.includedirs]), 115 | "{static_library}": repr(installed.static_library), 116 | "{shared_library}": repr(installed.shared_library), 117 | "{rules_ruby_workspace}": RULES_RUBY_WORKSPACE_NAME, 118 | }, 119 | executable = False, 120 | ) 121 | 122 | ruby_runtime = repository_rule( 123 | implementation = _ruby_runtime_impl, 124 | attrs = { 125 | "version": attr.string(default = "host"), 126 | "_buildfile_template": attr.label( 127 | default = "%s//ruby/private/toolchains:BUILD.runtime.tpl" % ( 128 | RULES_RUBY_WORKSPACE_NAME 129 | ), 130 | allow_single_file = True, 131 | ), 132 | "_interpreter_wrapper_template": attr.label( 133 | default = "%s//ruby/private/toolchains:interpreter_wrapper.tpl" % ( 134 | RULES_RUBY_WORKSPACE_NAME 135 | ), 136 | allow_single_file = True, 137 | ), 138 | }, 139 | ) 140 | -------------------------------------------------------------------------------- /ruby/private/bundle/create_bundle_build_file.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | TEMPLATE = <<~MAIN_TEMPLATE 5 | load( 6 | "{workspace_name}//ruby:defs.bzl", 7 | "rb_library", 8 | ) 9 | 10 | package(default_visibility = ["//visibility:public"]) 11 | 12 | rb_library( 13 | name = "activate_gems", 14 | srcs = ["activate_gems.rb"], 15 | rubyopt = ["-r{runfiles_path}/activate_gems.rb"], 16 | ) 17 | 18 | rb_library( 19 | name = "bundler", 20 | srcs = glob( 21 | include = [ 22 | "bundler/**/*", 23 | ], 24 | ), 25 | includes = ["bundler/gems/bundler-{bundler_version}/lib"], 26 | rubyopt = ["-r{runfiles_path}/bundler/gems/bundler-{bundler_version}/lib/bundler.rb"], 27 | ) 28 | 29 | # PULL EACH GEM INDIVIDUALLY 30 | MAIN_TEMPLATE 31 | 32 | # The build_complete file is only silence the 'extensions' are not built warnings. They are built. 33 | # TODO: Replace the extensions ** with the platform (eg. x86_64-darwin-19) 34 | GEM_TEMPLATE = <<~GEM_TEMPLATE 35 | rb_library( 36 | name = "{name}", 37 | srcs = glob( 38 | include = [ 39 | "lib/ruby/{ruby_version}/gems/{name}-{version}*/**", 40 | "lib/ruby/{ruby_version}/specifications/{name}-{version}*.gemspec", 41 | "lib/ruby/{ruby_version}/cache/{name}-{version}*.gem", 42 | "lib/ruby/{ruby_version}/extensions/**/{ruby_version}/{name}-{version}/gem.build_complete", 43 | "bin/*" 44 | ], 45 | exclude = {exclude}, 46 | ), 47 | deps = {deps}, 48 | includes = ["lib/ruby/{ruby_version}/gems/{name}-{version}*/lib"], 49 | ) 50 | GEM_TEMPLATE 51 | 52 | ALL_GEMS = <<~ALL_GEMS 53 | rb_library( 54 | name = "all_gems", 55 | srcs = glob( 56 | {gems_lib_files}, 57 | ), 58 | includes = {gems_lib_paths}, 59 | ) 60 | ALL_GEMS 61 | 62 | GEM_LIB_PATH = lambda do |ruby_version, gem_name, gem_version| 63 | "lib/ruby/#{ruby_version}/gems/#{gem_name}-#{gem_version}*/lib" 64 | end 65 | 66 | require 'bundler' 67 | require 'json' 68 | require 'stringio' 69 | require 'fileutils' 70 | require 'tempfile' 71 | 72 | class BundleBuildFileGenerator 73 | attr_reader :workspace_name, 74 | :repo_name, 75 | :build_file, 76 | :gemfile_lock, 77 | :excludes, 78 | :ruby_version, 79 | :bundler_version 80 | 81 | # rubocop:disable Metrics/ParameterLists 82 | def initialize(workspace_name:, 83 | repo_name:, 84 | bundler_version:, 85 | build_file: 'BUILD.bazel', 86 | gemfile_lock: 'Gemfile.lock', 87 | excludes: nil) 88 | # rubocop:enable Metrics/ParameterLists 89 | @workspace_name = workspace_name 90 | @repo_name = repo_name 91 | @build_file = build_file 92 | @gemfile_lock = gemfile_lock 93 | @excludes = excludes 94 | @bundler_version = bundler_version 95 | # This attribute returns 0 as the third minor version number, which happens to be 96 | # what Ruby uses in the PATH to gems, eg. ruby 2.6.5 would have a folder called 97 | # ruby/2.6.0/gems for all minor versions of 2.6.* 98 | @ruby_version ||= (RUBY_VERSION.split('.')[0..1] << 0).join('.') 99 | end 100 | 101 | def generate! 102 | # when we append to a string many times, using StringIO is more efficient. 103 | template_out = StringIO.new 104 | 105 | # In Bazel we want to use __FILE__ because __dir__points to the actual sources, and we are 106 | # using symlinks here. 107 | # 108 | # rubocop:disable Style/ExpandPathArguments 109 | bin_folder = File.expand_path('../bin', __FILE__) 110 | binaries = Dir.glob("#{bin_folder}/*").map do |binary| 111 | 'bin/' + File.basename(binary) if File.executable?(binary) 112 | end 113 | # rubocop:enable Style/ExpandPathArguments 114 | 115 | template_out.puts TEMPLATE 116 | .gsub('{workspace_name}', workspace_name) 117 | .gsub('{repo_name}', repo_name) 118 | .gsub('{ruby_version}', ruby_version) 119 | .gsub('{binaries}', binaries.to_s) 120 | .gsub('{runfiles_path}', runfiles_path) 121 | .gsub('{bundler_version}', bundler_version) 122 | 123 | # strip bundler version so we can process this file 124 | remove_bundler_version! 125 | # Append to the end specific gem libraries and dependencies 126 | bundle = Bundler::LockfileParser.new(Bundler.read_file(gemfile_lock)) 127 | gem_lib_paths = [] 128 | bundle.specs.each { |spec| register_gem(spec, template_out, gem_lib_paths) } 129 | 130 | template_out.puts ALL_GEMS 131 | .gsub('{gems_lib_files}', gem_lib_paths.map { |p| "#{p}/**/*.rb" }.to_s) 132 | .gsub('{gems_lib_paths}', gem_lib_paths.to_s) 133 | 134 | ::File.open(build_file, 'w') { |f| f.puts template_out.string } 135 | end 136 | 137 | private 138 | 139 | def runfiles_path 140 | "${RUNFILES_DIR}/#{repo_name}" 141 | end 142 | 143 | # This method scans the contents of the Gemfile.lock and if it finds BUNDLED WITH 144 | # it strips that line + the line below it, so that any version of bundler would work. 145 | def remove_bundler_version! 146 | contents = File.read(gemfile_lock) 147 | return unless contents =~ /BUNDLED WITH/ 148 | 149 | temp_gemfile_lock = "#{gemfile_lock}.no-bundle-version" 150 | system %(sed -n '/BUNDLED WITH/q;p' "#{gemfile_lock}" > #{temp_gemfile_lock}) 151 | if File.symlink?(gemfile_lock) 152 | ::FileUtils.rm_f(gemfile_lock) # it's just a symlink 153 | end 154 | ::FileUtils.move(temp_gemfile_lock, gemfile_lock, force: true) 155 | end 156 | 157 | def register_gem(spec, template_out, gem_lib_paths) 158 | gem_lib_paths << GEM_LIB_PATH[ruby_version, spec.name, spec.version] 159 | deps = spec.dependencies.map { |d| ":#{d.name}" } 160 | deps += [':activate_gems'] 161 | 162 | exclude_array = excludes[spec.name] || [] 163 | # We want to exclude files and folder with spaces in them 164 | exclude_array += ['**/* *.*', '**/* */*'] 165 | 166 | template_out.puts GEM_TEMPLATE 167 | .gsub('{exclude}', exclude_array.to_s) 168 | .gsub('{name}', spec.name) 169 | .gsub('{version}', spec.version.to_s) 170 | .gsub('{deps}', deps.to_s) 171 | .gsub('{repo_name}', repo_name) 172 | .gsub('{ruby_version}', ruby_version) 173 | end 174 | end 175 | 176 | if $PROGRAM_NAME == __FILE__ 177 | if ARGV.length != 6 178 | warn("USAGE: #{$PROGRAM_NAME} BUILD.bazel Gemfile.lock repo-name [excludes-json] workspace-name bundler-version") 179 | exit(1) 180 | end 181 | 182 | build_file, gemfile_lock, repo_name, excludes, workspace_name, bundler_version, * = *ARGV 183 | 184 | BundleBuildFileGenerator.new(build_file: build_file, 185 | gemfile_lock: gemfile_lock, 186 | repo_name: repo_name, 187 | excludes: JSON.parse(excludes), 188 | workspace_name: workspace_name, 189 | bundler_version: bundler_version).generate! 190 | end 191 | -------------------------------------------------------------------------------- /ruby/private/bundle/bundle.bzl: -------------------------------------------------------------------------------- 1 | load("//ruby/private:constants.bzl", "RULES_RUBY_WORKSPACE_NAME") 2 | load("//ruby/private:providers.bzl", "RubyRuntimeContext") 3 | 4 | DEFAULT_BUNDLER_VERSION = "2.1.2" 5 | BUNDLE_BIN_PATH = "bin" 6 | BUNDLE_PATH = "lib" 7 | 8 | SCRIPT_INSTALL_BUNDLER = "download_bundler.rb" 9 | SCRIPT_ACTIVATE_GEMS = "activate_gems.rb" 10 | SCRIPT_BUILD_FILE_GENERATOR = "create_bundle_build_file.rb" 11 | 12 | # Runs bundler with arbitrary arguments 13 | # eg: run_bundler(runtime_ctx, [ "lock", " --gemfile", "Gemfile.rails5" ]) 14 | def run_bundler(runtime_ctx, bundler_arguments): 15 | # Now we are running bundle install 16 | args = [ 17 | runtime_ctx.interpreter, # ruby 18 | "-I", 19 | ".", 20 | "-I", # Used to tell Ruby where to load the library scripts 21 | BUNDLE_PATH, # Add vendor/bundle to the list of resolvers 22 | "bundler/gems/bundler-{}/exe/bundle".format(runtime_ctx.bundler_version), # our binary 23 | ] + bundler_arguments 24 | 25 | kwargs = {} 26 | 27 | if "BUNDLER_TIMEOUT" in runtime_ctx.ctx.os.environ: 28 | timeout_in_secs = runtime_ctx.ctx.os.environ["BUNDLER_TIMEOUT"] 29 | if timeout_in_secs.isdigit(): 30 | kwargs["timeout"] = int(timeout_in_secs) 31 | else: 32 | fail("'%s' is invalid value for BUNDLER_TIMEOUT. Must be an integer." % (timeout_in_secs)) 33 | 34 | return runtime_ctx.ctx.execute( 35 | args, 36 | quiet = False, 37 | # Need to run this command with GEM_HOME set so tgat the bin stubs can load the correct bundler 38 | environment = {"GEM_HOME": "bundler", "GEM_PATH": "bundler"}, 39 | **kwargs 40 | ) 41 | 42 | def install_bundler(runtime_ctx): 43 | args = [ 44 | runtime_ctx.interpreter, 45 | SCRIPT_INSTALL_BUNDLER, 46 | runtime_ctx.bundler_version, 47 | ] 48 | result = runtime_ctx.ctx.execute(args, environment = runtime_ctx.environment, quiet = False) 49 | if result.return_code: 50 | fail("Error installing bundler: {} {}".format(result.stdout, result.stderr)) 51 | 52 | def bundle_install(runtime_ctx): 53 | bundler_args = [ 54 | "install", # bundle install 55 | "--standalone", # Makes a bundle that can work without depending on Rubygems or Bundler at runtime. 56 | "--binstubs={}".format(BUNDLE_BIN_PATH), # Creates a directory and place any executables from the gem there. 57 | "--path={}".format(BUNDLE_PATH), # The location to install the specified gems to. 58 | "--jobs=10", # run a few jobs to ensure no gem install is blocking another 59 | ] 60 | 61 | if runtime_ctx.ctx.attr.full_index: 62 | bundler_args.append("--full-index") 63 | result = run_bundler(runtime_ctx, bundler_args) 64 | 65 | if result.return_code: 66 | fail("bundle install failed: %s%s" % (result.stdout, result.stderr)) 67 | 68 | def generate_bundle_build_file(runtime_ctx): 69 | # Create the BUILD file to expose the gems to the WORKSPACE 70 | # USAGE: ./create_bundle_build_file.rb BUILD.bazel Gemfile.lock repo-name [excludes-json] workspace-name 71 | args = [ 72 | runtime_ctx.interpreter, # ruby interpreter 73 | SCRIPT_BUILD_FILE_GENERATOR, # The template used to created bundle file 74 | "BUILD.bazel", # Bazel build file (can be empty) 75 | "Gemfile.lock", # Gemfile.lock where we list all direct and transitive dependencies 76 | runtime_ctx.ctx.name, # Name of the target 77 | repr(runtime_ctx.ctx.attr.excludes), 78 | RULES_RUBY_WORKSPACE_NAME, 79 | runtime_ctx.bundler_version, 80 | ] 81 | 82 | result = runtime_ctx.ctx.execute( 83 | args, 84 | # The build file generation script requires bundler so we add this to make 85 | # the correct version of bundler available 86 | environment = {"GEM_HOME": "bundler", "GEM_PATH": "bundler"}, 87 | quiet = False, 88 | ) 89 | if result.return_code: 90 | fail("build file generation failed: %s%s" % (result.stdout, result.stderr)) 91 | 92 | def _rb_bundle_impl(ctx): 93 | ctx.symlink(ctx.attr.gemfile, "Gemfile") 94 | ctx.symlink(ctx.attr.gemfile_lock, "Gemfile.lock") 95 | ctx.symlink(ctx.attr._create_bundle_build_file, SCRIPT_BUILD_FILE_GENERATOR) 96 | ctx.symlink(ctx.attr._install_bundler, SCRIPT_INSTALL_BUNDLER) 97 | ctx.symlink(ctx.attr._activate_gems, SCRIPT_ACTIVATE_GEMS) 98 | 99 | # Setup this provider that we pass around between functions for convenience 100 | runtime_ctx = RubyRuntimeContext( 101 | ctx = ctx, 102 | interpreter = ctx.path(ctx.attr.ruby_interpreter), 103 | environment = {"RUBYOPT": "--enable-gems"}, 104 | bundler_version = ctx.attr.bundler_version, 105 | ) 106 | 107 | # 1. Install the right version of the Bundler Gem 108 | install_bundler(runtime_ctx) 109 | 110 | # Create label for the Bundler executable 111 | bundler = Label("//:bundler/gems/bundler-{}/exe/bundle".format(runtime_ctx.bundler_version)) 112 | 113 | # Run bundle install 114 | bundle_install(runtime_ctx) 115 | 116 | # Generate the BUILD file for the bundle 117 | generate_bundle_build_file(runtime_ctx) 118 | 119 | rb_bundle = repository_rule( 120 | implementation = _rb_bundle_impl, 121 | attrs = { 122 | "ruby_sdk": attr.string( 123 | default = "@org_ruby_lang_ruby_toolchain", 124 | ), 125 | "ruby_interpreter": attr.label( 126 | default = "@org_ruby_lang_ruby_toolchain//:ruby", 127 | ), 128 | "gemfile": attr.label( 129 | allow_single_file = True, 130 | mandatory = True, 131 | ), 132 | "gemfile_lock": attr.label( 133 | allow_single_file = True, 134 | ), 135 | "version": attr.string( 136 | mandatory = False, 137 | ), 138 | "bundler_version": attr.string( 139 | default = DEFAULT_BUNDLER_VERSION, 140 | ), 141 | "excludes": attr.string_list_dict( 142 | doc = "List of glob patterns per gem to be excluded from the library", 143 | ), 144 | "full_index": attr.bool( 145 | default = False, 146 | doc = "Use --full-index for bundle install", 147 | ), 148 | "_install_bundler": attr.label( 149 | default = "%s//ruby/private/bundle:%s" % ( 150 | RULES_RUBY_WORKSPACE_NAME, 151 | SCRIPT_INSTALL_BUNDLER, 152 | ), 153 | allow_single_file = True, 154 | ), 155 | "_create_bundle_build_file": attr.label( 156 | default = "%s//ruby/private/bundle:%s" % ( 157 | RULES_RUBY_WORKSPACE_NAME, 158 | SCRIPT_BUILD_FILE_GENERATOR, 159 | ), 160 | doc = "Creates the BUILD file", 161 | allow_single_file = True, 162 | ), 163 | "_activate_gems": attr.label( 164 | default = "%s//ruby/private/bundle:%s" % ( 165 | RULES_RUBY_WORKSPACE_NAME, 166 | SCRIPT_ACTIVATE_GEMS, 167 | ), 168 | allow_single_file = True, 169 | ), 170 | }, 171 | ) 172 | -------------------------------------------------------------------------------- /ruby/tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") 2 | load( 3 | "//ruby:defs.bzl", 4 | "rb_binary", 5 | "rb_test", 6 | ) 7 | load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar") 8 | load( 9 | "@io_bazel_rules_docker//container:container.bzl", 10 | "container_image", 11 | ) 12 | 13 | # Checks if args are correctly passed to the ruby script. 14 | rb_test( 15 | name = "args_check_rb_test", 16 | size = "small", 17 | srcs = ["args_check.rb"], 18 | args = [ 19 | "foo", 20 | "bar", 21 | "baz", 22 | ], 23 | main = "args_check.rb", 24 | ) 25 | 26 | rb_binary( 27 | name = "args_check", 28 | srcs = ["args_check.rb"], 29 | ) 30 | 31 | # Checks if a rb_binary is a valid src in sh_* rules 32 | sh_test( 33 | name = "args_check_sh_test", 34 | size = "small", 35 | srcs = ["args_check"], 36 | args = [ 37 | "foo", 38 | "bar", 39 | "baz", 40 | ], 41 | ) 42 | 43 | rb_test( 44 | name = "include_order_check", 45 | size = "small", 46 | srcs = ["include_order_check.rb"], 47 | deps = [ 48 | "//ruby/tests/testdata:a", 49 | "//ruby/tests/testdata:b", 50 | "//ruby/tests/testdata:c", 51 | "//ruby/tests/testdata:f", 52 | ], 53 | ) 54 | 55 | # Tests if :ruby_bin can run in sh_binary 56 | sh_test( 57 | name = "runtime_run_rb_test", 58 | size = "small", 59 | srcs = ["runtime_run_rb_test.sh"], 60 | args = [ 61 | "$(location args_check.rb)", 62 | "foo", 63 | "bar", 64 | "baz", 65 | ], 66 | data = [ 67 | "args_check.rb", 68 | "@org_ruby_lang_ruby_toolchain//:ruby_bin", 69 | ], 70 | ) 71 | 72 | # Tests if :ruby_bin can run in genrule 73 | genrule( 74 | name = "generate_genrule_run_rb_test", 75 | outs = ["genrules_run_rb_test.sh"], 76 | cmd = " && ".join([ 77 | ("$(location @org_ruby_lang_ruby_toolchain//:ruby_bin) " + 78 | "$(location args_check.rb) foo bar baz"), 79 | "echo '#!/bin/sh -e' > $@", 80 | "echo true >> $@", 81 | ]), 82 | message = "Running ruby_bin in genrule", 83 | output_to_bindir = 1, 84 | tools = [ 85 | "args_check.rb", 86 | "@org_ruby_lang_ruby_toolchain//:ruby_bin", 87 | "@org_ruby_lang_ruby_toolchain//:runtime", 88 | ], 89 | ) 90 | 91 | sh_test( 92 | name = "genrule_run_rb_test", 93 | size = "small", 94 | srcs = ["genrules_run_rb_test.sh"], 95 | ) 96 | 97 | ## Runfiles resolution tests 98 | # 99 | # As explained in 100 | # https://github.com/bazelbuild/rules_nodejs/blob/be7232ecfd487432072938f3a39886be32f02606/internal/node/node_launcher.sh#L49, 101 | # the runfiles resolution in a program X need to support the following cases 102 | # 103 | # 1a) directly by a user, with $0 in the output tree 104 | # 1b) via 'bazel run' (similar to case 1a) 105 | # 2) directly by a user, with $0 in X's runfiles 106 | # 3) by another program Y which has a data dependency on X, with $0 in Y's 107 | # runfiles 108 | # 4a) via 'bazel test' 109 | # 4b) case 3 in the context of a test 110 | # 5a) by a genrule cmd, with $0 in the output tree 111 | # 6a) case 3 in the context of a genrule 112 | # 113 | # Also all of the cases above must correctly configure environment variables 114 | # so that their subprocesses whose binaries are generated by Bazel can run with 115 | # their runfiles. 116 | 117 | rb_binary( 118 | name = "load_path_in_runfiles", 119 | srcs = ["load_path_in_runfiles_test.rb"], 120 | main = "load_path_in_runfiles_test.rb", 121 | deps = [ 122 | "//ruby/tests/testdata:g", 123 | "@coinbase_rules_ruby_ruby_tests_testdata_another_workspace//baz/qux:j", 124 | ], 125 | ) 126 | 127 | sh_binary( 128 | name = "load_path_in_runfiles_sh_binary", 129 | srcs = [":load_path_in_runfiles"], 130 | ) 131 | 132 | # runfiles resolution test (case 4a) 133 | rb_test( 134 | name = "load_path_in_runfiles_test_4a", 135 | size = "small", 136 | srcs = ["load_path_in_runfiles_test.rb"], 137 | main = "load_path_in_runfiles_test.rb", 138 | deps = [ 139 | "//ruby/tests/testdata:g", 140 | "@coinbase_rules_ruby_ruby_tests_testdata_another_workspace//baz/qux:j", 141 | ], 142 | ) 143 | 144 | # runfiles resolution test (case 4b) 145 | sh_test( 146 | name = "load_path_in_runfiles_test_4b", 147 | size = "small", 148 | srcs = ["load_path_in_runfiles_test_3.sh"], 149 | data = [":load_path_in_runfiles_sh_binary"], 150 | ) 151 | 152 | genrule( 153 | name = "generate_loadpath_test_driver_3", 154 | outs = ["load_path_in_runfiles_test_3.sh"], 155 | cmd = " && ".join([ 156 | "echo '#!/bin/sh -e' > $@", 157 | "echo 'exec ruby/tests/load_path_in_runfiles' >> $@", 158 | ]), 159 | executable = True, 160 | ) 161 | 162 | # runfiles resolution test (case 5a) 163 | sh_test( 164 | name = "load_path_in_runfiles_test_5a", 165 | size = "small", 166 | srcs = ["load_path_in_runfiles_test_5a.sh"], 167 | ) 168 | 169 | genrule( 170 | name = "dummy_genfile_load_path_in_runfiles", 171 | outs = ["load_path_in_runfiles_test_5a.sh"], 172 | cmd = " && ".join([ 173 | "$(location :load_path_in_runfiles)", 174 | "echo '#!/bin/sh -e' > $@", 175 | "echo 'true' >> $@", 176 | ]), 177 | executable = True, 178 | message = "Running :load_path_in_runfiles in genrule", 179 | tools = [":load_path_in_runfiles"], 180 | ) 181 | 182 | # TODO(yugui) Add a test for case 6a. 183 | 184 | ## end of Runfiles resolution tests 185 | 186 | cc_binary( 187 | name = "example_ext.so", 188 | testonly = True, 189 | srcs = ["example_ext.c"], 190 | linkshared = True, 191 | deps = ["@org_ruby_lang_ruby_toolchain//:headers"], 192 | ) 193 | 194 | cc_library( 195 | name = "example_ext_lib", 196 | testonly = True, 197 | srcs = ["example_ext.c"], 198 | linkstatic = True, 199 | tags = ["manual"], 200 | deps = ["@org_ruby_lang_ruby_toolchain//:headers"], 201 | alwayslink = True, 202 | ) 203 | 204 | apple_binary( 205 | name = "example_ext", 206 | testonly = True, 207 | binary_type = "loadable_bundle", 208 | linkopts = [ 209 | "-undefined,dynamic_lookup", 210 | "-multiply_defined,suppress", 211 | ], 212 | platform_type = "macos", 213 | tags = ["manual"], 214 | deps = [ 215 | ":example_ext_lib", 216 | ], 217 | ) 218 | 219 | genrule( 220 | name = "gen_example_ext", 221 | testonly = True, 222 | srcs = [":example_ext"], 223 | outs = ["example_ext.bundle"], 224 | cmd = "cp $< $@", 225 | tags = ["manual"], 226 | ) 227 | 228 | config_setting( 229 | name = "requires_bundle", 230 | constraint_values = ["@platforms//os:osx"], 231 | ) 232 | 233 | rb_test( 234 | name = "ext_test", 235 | size = "small", 236 | srcs = ["ext_test.rb"], 237 | data = select({ 238 | ":requires_bundle": ["example_ext.bundle"], 239 | "//conditions:default": ["example_ext.so"], 240 | }), 241 | main = "ext_test.rb", 242 | ) 243 | 244 | ## Containerization test 245 | 246 | # TODO(yugui) Make it easier to build a tar with the right runfiles structure 247 | pkg_tar( 248 | name = "load_path_in_runfiles_container_layer", 249 | srcs = [":load_path_in_runfiles"], 250 | include_runfiles = True, 251 | package_dir = "/app", 252 | remap_paths = { 253 | "ruby": "load_path_in_runfiles.runfiles/coinbase_rules_ruby/ruby", 254 | ".": "load_path_in_runfiles.runfiles/", 255 | }, 256 | strip_prefix = "dummy", 257 | symlinks = { 258 | "/app/load_path_in_runfiles.runfiles/coinbase_rules_ruby/external": "/app/load_path_in_runfiles.runfiles", 259 | "/app/load_path_in_runfiles": "/app/load_path_in_runfiles.runfiles/coinbase_rules_ruby/ruby/tests/load_path_in_runfiles", 260 | }, 261 | ) 262 | 263 | container_image( 264 | name = "load_path_in_runfiles_container_image", 265 | base = "@ruby_base_container//image", 266 | entrypoint = ["/app/load_path_in_runfiles"], 267 | tars = [":load_path_in_runfiles_container_layer"], 268 | ) 269 | 270 | sh_test( 271 | name = "load_path_in_runfiles_container_test", 272 | size = "small", 273 | srcs = ["container_test.sh"], 274 | args = [ 275 | "$(location :load_path_in_runfiles_container_image)", 276 | "bazel/ruby/tests:load_path_in_runfiles_container_image", 277 | ], 278 | data = [":load_path_in_runfiles_container_image"], 279 | tags = ["docker"], 280 | ) 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | * [Usage](#usage) 4 | * [WORKSPACE File](#workspace-file) 5 | * [BUILD.bazel files](#buildbazel-files) 6 | * [Rules](#rules) 7 | * [rb_library](#rb_library) 8 | * [rb_binary](#rb_binary) 9 | * [rb_test](#rb_test) 10 | * [rb_bundle](#rb_bundle) 11 | * [rb_gem](#rb_gem) 12 | * [What's coming next](#whats-coming-next) 13 | * [Contributing](#contributing) 14 | * [Setup](#setup) 15 | * [OSX Setup Script](#os-setup-script) 16 | * [Issues During Setup](#issues-during-setup) 17 | * [Developing Rules](#developing-rules) 18 | * [Running Tests](#running-tests) 19 | * [Linter](#linter) 20 | * [Copyright](#copyright) 21 | 22 | 23 | 24 | ### Build Status 25 | 26 | | Build | Status | 27 | |---------: |--------------------------------------------------------------------------------------------------------------------------------------------------- | 28 | | CircleCI Master: | [![CircleCI](https://circleci.com/gh/coinbase/rules_ruby.svg?style=svg)](https://circleci.com/gh/coinbase/rules_ruby) | 29 | 30 | 31 | # Rules Ruby 32 | 33 | Ruby rules for [Bazel](https://bazel.build). 34 | 35 | ** Current Status:** *Work in progress.* 36 | 37 | Note: we have a short guide on [Building your first Ruby Project](https://github.com/coinbase/rules_ruby/wiki/Build-your-ruby-project) on the Wiki. We encourage you to check it out. 38 | 39 | ## Usage 40 | 41 | ### `WORKSPACE` File 42 | 43 | Add `rules_ruby_dependencies` and `ruby_register_toolchains` into your `WORKSPACE` file. 44 | 45 | ```python 46 | # To get the latest, grab the 'master' branch. 47 | 48 | git_repository( 49 | name = "coinbase_rules_ruby", 50 | remote = "https://github.com/coinbase/rules_ruby.git", 51 | branch = "master", 52 | ) 53 | 54 | load( 55 | "@coinbase_rules_ruby//ruby:deps.bzl", 56 | "ruby_register_toolchains", 57 | "rules_ruby_dependencies", 58 | ) 59 | 60 | rules_ruby_dependencies() 61 | 62 | ruby_register_toolchains() 63 | ``` 64 | 65 | Next, add any external Gem dependencies you may have via `rb_bundle` command. 66 | The name of the bundle becomes a reference to this particular Gemfile.lock. 67 | 68 | Install external gems that can be later referenced as `@//:`, 69 | and the executables from each gem can be accessed as `@` 70 | for instance, `@bundle//:bin/rubocop`. 71 | 72 | You can install more than one bundle per WORKSPACE, but that's not recommended. 73 | 74 | ```python 75 | rb_bundle( 76 | name = "bundle", 77 | gemfile = ":Gemfile", 78 | gemfile_lock = ":Gemfile.lock", 79 | bundler_version = "2.1.2", 80 | full_index = True, 81 | ) 82 | 83 | rb_bundle( 84 | name = "bundle_app_shopping", 85 | gemfile = "//apps/shopping:Gemfile", 86 | gemfile_lock = "//apps/shopping:Gemfile.lock", 87 | bundler_version = "2.1.2", 88 | full_index = True, 89 | ) 90 | ``` 91 | 92 | ### `BUILD.bazel` files 93 | 94 | Add `rb_library`, `rb_binary` or `rb_test` into your `BUILD.bazel` files. 95 | 96 | ```python 97 | load( 98 | "@coinbase_rules_ruby//ruby:defs.bzl", 99 | "rb_binary", 100 | "rb_library", 101 | "rb_test", 102 | "rb_rspec", 103 | ) 104 | 105 | rb_library( 106 | name = "foo", 107 | srcs = glob(["lib/**/*.rb"]), 108 | includes = ["lib"], 109 | deps = [ 110 | "@bundle//:activesupport", 111 | "@bundle//:awesome_print", 112 | "@bundle//:rubocop", 113 | ] 114 | ) 115 | 116 | rb_binary( 117 | name = "bar", 118 | srcs = ["bin/bar"], 119 | deps = [":foo"], 120 | ) 121 | 122 | rb_test( 123 | name = "foo-test", 124 | srcs = ["test/foo_test.rb"], 125 | deps = [":foo"], 126 | ) 127 | 128 | rb_rspec( 129 | name = "foo-spec", 130 | specs = glob(["spec/**/*.rb"]), 131 | rspec_args = { "--format": "progress" }, 132 | deps = [":foo"] 133 | } 134 | 135 | ``` 136 | 137 | ## Rules 138 | 139 | The following diagram attempts to capture the implementation behind `rb_library` that depends on the result of `bundle install`, and a `rb_binary` that depends on both: 140 | 141 | ![Ruby Rules](docs/img/rules_ruby.png) 142 | 143 | 144 | ### `rb_library` 145 | 146 |
147 | rb_library(name, deps, srcs, data, compatible_with, deprecation, distribs, features, licenses, restricted_to, tags, testonly, toolchains, visibility)
148 | 
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 167 | 168 | 169 | 170 | 177 | 178 | 179 | 180 | 188 | 189 | 190 | 191 | 198 | 199 | 200 | 201 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 |
Attributes
name 164 | Name, required 165 |

A unique name for this rule.

166 |
srcs 171 | List of Labels, optional 172 |

173 | List of .rb files. 174 |

175 |

At least srcs or deps must be present

176 |
deps 181 | List of labels, optional 182 |

183 | List of targets that are required by the srcs Ruby 184 | files. 185 |

186 |

At least srcs or deps must be present

187 |
includes 192 | List of strings, optional 193 |

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

197 |
rubyopt 202 | List of strings, optional 203 |

204 | List of options to be passed to the Ruby interpreter at runtime. 205 |

206 |

207 | NOTE: -I option should usually go to includes attribute. 208 |

209 |
And other common attributes
218 | 219 | ### `rb_binary` 220 | 221 |
222 | rb_binary(name, deps, srcs, data, main, compatible_with, deprecation, distribs, features, licenses, restricted_to, tags, testonly, toolchains, visibility, args, output_licenses)
223 | 
224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 242 | 243 | 244 | 245 | 251 | 252 | 253 | 254 | 261 | 262 | 263 | 264 | 269 | 270 | 271 | 272 | 279 | 280 | 281 | 282 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 |
Attributes
name 239 | Name, required 240 |

A unique name for this rule.

241 |
srcs 246 | List of Labels, required 247 |

248 | List of .rb files. 249 |

250 |
deps 255 | List of labels, optional 256 |

257 | List of targets that are required by the srcs Ruby 258 | files. 259 |

260 |
main 265 | Label, optional 266 |

The entrypoint file. It must be also in srcs.

267 |

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

268 |
includes 273 | List of strings, optional 274 |

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

278 |
rubyopt 283 | List of strings, optional 284 |

285 | List of options to be passed to the Ruby interpreter at runtime. 286 |

287 |

288 | NOTE: -I option should usually go to includes attribute. 289 |

290 |
And other common attributes
299 | 300 | ### `rb_test` 301 | 302 |
303 | rb_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)
304 | 
305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 323 | 324 | 325 | 326 | 332 | 333 | 334 | 335 | 342 | 343 | 344 | 345 | 350 | 351 | 352 | 353 | 360 | 361 | 362 | 363 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 |
Attributes
name 320 | Name, required 321 |

A unique name for this rule.

322 |
srcs 327 | List of Labels, required 328 |

329 | List of .rb files. 330 |

331 |
deps 336 | List of labels, optional 337 |

338 | List of targets that are required by the srcs Ruby 339 | files. 340 |

341 |
main 346 | Label, optional 347 |

The entrypoint file. It must be also in srcs.

348 |

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

349 |
includes 354 | List of strings, optional 355 |

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

359 |
rubyopt 364 | List of strings, optional 365 |

366 | List of options to be passed to the Ruby interpreter at runtime. 367 |

368 |

369 | NOTE: -I option should usually go to includes attribute. 370 |

371 |
And other common attributes
380 | 381 | ### `rb_bundle` 382 | 383 | Installs gems with Bundler, and make them available as a `rb_library`. 384 | 385 | Example: `WORKSPACE`: 386 | 387 | ```python 388 | git_repository( 389 | name = "coinbase_rules_ruby", 390 | remote = "https://github.com/coinbase/rules_ruby.git", 391 | tag = "v0.1.0", 392 | ) 393 | 394 | load( 395 | "@coinbase_rules_ruby//ruby:deps.bzl", 396 | "ruby_register_toolchains", 397 | "rules_ruby_dependencies", 398 | ) 399 | 400 | rules_ruby_dependencies() 401 | 402 | ruby_register_toolchains() 403 | 404 | load("@coinbase_rules_ruby//ruby:defs.bzl", "rb_bundle") 405 | 406 | rb_bundle( 407 | name = "gems", 408 | gemfile = "//:Gemfile", 409 | gemfile_lock = "//:Gemfile.lock", 410 | ) 411 | ``` 412 | 413 | Example: `lib/BUILD.bazel`: 414 | 415 | ```python 416 | rb_library( 417 | name = "foo", 418 | srcs = ["foo.rb"], 419 | deps = ["@gems//:all"], 420 | ) 421 | ``` 422 | 423 | Or declare many gems in your `Gemfile`, and only use some of them in each ruby library: 424 | 425 | ```python 426 | rb_binary( 427 | name = "rubocop", 428 | srcs = [":foo", ".rubocop.yml"], 429 | args = ["-P", "-D", "-c" ".rubocop.yml"], 430 | main = "@gems//:bin/rubocop", 431 | deps = ["@gems//:rubocop"], 432 | ) 433 | ``` 434 | 435 |
436 | rb_bundle(name, gemfile, gemfile_lock, bundler_version = "2.1.2")
437 | 
438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 455 | 456 | 457 | 458 | 464 | 465 | 466 | 467 | 472 | 473 | 474 | 475 | 480 | 481 | 482 | 483 | 487 | 488 | 489 |
Attributes
name 452 | Name, required 453 |

A unique name for this rule.

454 |
gemfile 459 | Label, required 460 |

461 | The Gemfile which Bundler runs with. 462 |

463 |
gemfile_lock 468 | Label, required 469 |

The Gemfile.lock which Bundler runs with.

470 |

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

471 |
bundler_version 476 | String, optional 477 |

The Version of Bundler to use. Defaults to 2.1.2.

478 |

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

479 |
full_index 484 | Bool, optional 485 |

Set to True to add the --full-index option to the bundle install. Can improve performance.

486 |
490 | 491 | ## rb_gem 492 | Used to generate a zipped gem containing its srcs, dependencies and a gemspec. 493 | 494 |
495 | rb_gem(name, gem_name, version, srcs, authors, deps, data, includes)
496 | 
497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 514 | 515 | 516 | 517 | 521 | 522 | 523 | 524 | 525 | 533 | 534 | 535 | 536 | 543 | 544 | 545 | 546 | 553 | 554 | 555 | 556 | 564 | 565 | 566 |
Attributes
name 511 | Name, required 512 |

A unique name for this rule.

513 |
gem_name 518 | Name of the gem, required 519 |

The name of the gem to be generated.

520 |
version 526 | Label, required 527 |

528 | The version of the gem. Is used to name the output file, 529 | which becomes name-version.zip, and also 530 | included in the Gemspec. 531 |

532 |
authors 537 | List of Strings, required 538 |

539 | List of human readable names of the gem authors. 540 | Required to generate a valid gemspec. 541 |

542 |
srcs 547 | List of Labels, optional 548 |

549 | List of .rb files. 550 |

551 |

At least srcs or deps must be present

552 |
deps 557 | List of labels, optional 558 |

559 | List of targets that are required by the srcs Ruby 560 | files. 561 |

562 |

At least srcs or deps must be present

563 |
567 | 568 | ## What's coming next 569 | 570 | 1. Building native extensions in gems with Bazel 571 | 2. Using a specified version of Ruby. 572 | 3. Releasing your gems with Bazel 573 | 574 | ## Contributing 575 | 576 | We welcome contributions to RulesRuby. 577 | 578 | You may notice that there is more than one Bazel WORKSPACE inside this repo. There is one in `examples/simple_script` for instance, because 579 | we use this example to validate and test the rules. So be mindful whether your current directory contains `WORKSPACE` file or not. 580 | 581 | ### Setup 582 | 583 | #### OSX Setup Script 584 | 585 | You will need Homebrew installed prior to running the script. 586 | 587 | After that, cd into the top level folder and run the setup script in your Terminal: 588 | 589 | ```bash 590 | ❯ bin/setup-darwin 591 | ``` 592 | ##### Issues During Setup 593 | 594 | **Please report any errors as Issues on Github.** 595 | 596 | ### Developing Rules 597 | 598 | Besides making yourself familiar with the existing code, and [Bazel documentation on writing rules](https://docs.bazel.build/versions/master/skylark/concepts.html), you might want to follow this order: 599 | 600 | 1. Setup dev tools as described in the [setup](#Setup) section. 601 | 3. hack, hack, hack... 602 | 4. Make sure all tests pass — you can run individual Bazel test commands from the inside. 603 | 604 | * `bazel test //...` 605 | * `cd examples/simple_script && bazel test //...` 606 | 607 | 4. Open a pull request in Github, and please be as verbose as possible in your description. 608 | 609 | In general, it's always a good idea to ask questions first — you can do so by creating an issue. 610 | 611 | ### Running Tests 612 | 613 | After running setup, and since this is a bazel repo you can use Bazel commands: 614 | 615 | ```bazel 616 | bazel build //...:all 617 | bazel query //...:all 618 | bazel test //...:all 619 | ``` 620 | 621 | But to run tests inside each sub-WORKSPACE, you will need to repeat that in each sub-folder. 622 | 623 | ### Linter 624 | 625 | We are using RuboCop for ruby and Buildifier for Bazel. Both can be run using bazel: 626 | 627 | ```bash 628 | bazel run //:buildifier 629 | ``` 630 | 631 | ## Copyright 632 | 633 | © 2018-2019 Yuki Yugui Sonoda & BazelRuby Authors 634 | 635 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 636 | 637 | 638 | 639 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, 640 | 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. 641 | --------------------------------------------------------------------------------