diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b26882e0..f3a6f456 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -83,9 +83,10 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0.6'
- - run: bazel test ...
+ - run: bazel build ...
- run: bazel run lib/gem:add-numbers 2
- run: bazel run lib/gem:print-version
+ - run: bazel test ...
- if: failure() && runner.debug == '1'
uses: mxschmitt/action-tmate@v3
diff --git a/docs/rules.md b/docs/rules.md
index 6cd24841..e6659cf1 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -151,7 +151,7 @@ rake, version 10.5.0
| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| name | A unique name for this target. | Name | required | |
-| data | List of non-Ruby source files used to build the library. | List of labels | optional | []
|
+| data | List of runtime dependencies needed by a program that depends on this library. | List of labels | optional | []
|
| deps | List of other Ruby libraries the target depends on. | List of labels | optional | []
|
| env | Environment variables to use during execution. | Dictionary: String -> String | optional | {}
|
| env_inherit | List of environment variable names to be inherited by the test runner. | List of strings | optional | []
|
@@ -258,7 +258,7 @@ INFO: Build completed successfully, 2 total actions
| :------------- | :------------- | :------------- | :------------- | :------------- |
| name | A unique name for this target. | Name | required | |
| bundle_env | List of bundle environment variables to set when building the library. | Dictionary: String -> String | optional | {}
|
-| data | List of non-Ruby source files used to build the library. | List of labels | optional | []
|
+| data | List of runtime dependencies needed by a program that depends on this library. | List of labels | optional | []
|
| deps | List of other Ruby libraries the target depends on. | List of labels | optional | []
|
| gemspec | Gemspec file to use for gem building. | Label | required | |
| srcs | List of Ruby source files used to build the library. | List of labels | optional | []
|
@@ -336,7 +336,7 @@ Successfully registered gem: example (0.1.0)
| :------------- | :------------- | :------------- | :------------- | :------------- |
| name | A unique name for this target. | Name | required | |
| bundle_env | List of bundle environment variables to set when building the library. | Dictionary: String -> String | optional | {}
|
-| data | List of non-Ruby source files used to build the library. | List of labels | optional | []
|
+| data | List of runtime dependencies needed by a program that depends on this library. | List of labels | optional | []
|
| deps | List of other Ruby libraries the target depends on. | List of labels | optional | []
|
| env | Environment variables to use during execution. | Dictionary: String -> String | optional | {}
|
| env_inherit | List of environment variable names to be inherited by the test runner. | List of strings | optional | []
|
@@ -437,7 +437,7 @@ using other rules.
| :------------- | :------------- | :------------- | :------------- | :------------- |
| name | A unique name for this target. | Name | required | |
| bundle_env | List of bundle environment variables to set when building the library. | Dictionary: String -> String | optional | {}
|
-| data | List of non-Ruby source files used to build the library. | List of labels | optional | []
|
+| data | List of runtime dependencies needed by a program that depends on this library. | List of labels | optional | []
|
| deps | List of other Ruby libraries the target depends on. | List of labels | optional | []
|
| srcs | List of Ruby source files used to build the library. | List of labels | optional | []
|
@@ -597,7 +597,7 @@ root = File.expand_path(__dir__)
| Name | Description | Type | Mandatory | Default |
| :------------- | :------------- | :------------- | :------------- | :------------- |
| name | A unique name for this target. | Name | required | |
-| data | List of non-Ruby source files used to build the library. | List of labels | optional | []
|
+| data | List of runtime dependencies needed by a program that depends on this library. | List of labels | optional | []
|
| deps | List of other Ruby libraries the target depends on. | List of labels | optional | []
|
| env | Environment variables to use during execution. | Dictionary: String -> String | optional | {}
|
| env_inherit | List of environment variable names to be inherited by the test runner. | List of strings | optional | []
|
diff --git a/examples/gem/lib/gem/BUILD b/examples/gem/lib/gem/BUILD
index 0a83aab3..8f21943f 100644
--- a/examples/gem/lib/gem/BUILD
+++ b/examples/gem/lib/gem/BUILD
@@ -1,3 +1,4 @@
+load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
load("@rules_ruby//ruby:defs.bzl", "rb_binary", "rb_library")
package(default_visibility = ["//:__subpackages__"])
@@ -32,3 +33,15 @@ rb_binary(
args = ["lib/gem/version.rb"],
deps = [":version"],
)
+
+run_binary(
+ name = "perform-addition",
+ srcs = [":add"],
+ outs = ["addition-result"],
+ args = [
+ "1",
+ "2",
+ "$(location :addition-result)",
+ ],
+ tool = ":add-numbers",
+)
diff --git a/examples/gem/lib/gem/add.rb b/examples/gem/lib/gem/add.rb
index 65defc09..94615c7e 100755
--- a/examples/gem/lib/gem/add.rb
+++ b/examples/gem/lib/gem/add.rb
@@ -17,7 +17,13 @@ def result
end
if __FILE__ == $PROGRAM_NAME
- raise "Pass two numbers to sum: #{ARGV}" unless ARGV.size == 2
+ raise "Pass two numbers to sum: #{ARGV}" if ARGV.size < 2
- puts GEM::Add.new(*ARGV.map(&:to_i)).result
+ one, two, output = *ARGV
+ result = GEM::Add.new(Integer(one), Integer(two)).result
+ if output
+ File.write(output, result)
+ else
+ puts result
+ end
end
diff --git a/ruby/private/BUILD b/ruby/private/BUILD
index fe40bda6..bc32b1ec 100644
--- a/ruby/private/BUILD
+++ b/ruby/private/BUILD
@@ -1,12 +1,6 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
-exports_files([
- "binary/binary.cmd.tpl",
- "binary/binary.sh.tpl",
- "gem_build/gem_builder.rb.tpl",
-])
-
-exports_files(glob(["**/*.bzl"]))
+exports_files(["gem_build/gem_builder.rb.tpl"])
bzl_library(
name = "binary",
@@ -15,6 +9,7 @@ bzl_library(
deps = [
":library",
":providers",
+ "//ruby/private/binary:rlocation",
],
)
diff --git a/ruby/private/binary.bzl b/ruby/private/binary.bzl
index 8b9529b2..4cd69dca 100644
--- a/ruby/private/binary.bzl
+++ b/ruby/private/binary.bzl
@@ -7,8 +7,10 @@ load(
"get_bundle_env",
"get_transitive_data",
"get_transitive_deps",
+ "get_transitive_runfiles",
"get_transitive_srcs",
)
+load("//ruby/private/binary:rlocation.bzl", "BASH_RLOCATION_FUNCTION", "BATCH_RLOCATION_FUNCTION")
ATTRS = {
"main": attr.label(
@@ -30,19 +32,27 @@ Use a built-in `args` attribute to pass extra arguments to the script.
),
"_binary_cmd_tpl": attr.label(
allow_single_file = True,
- default = "@rules_ruby//ruby/private:binary/binary.cmd.tpl",
+ default = "@rules_ruby//ruby/private/binary:binary.cmd.tpl",
),
"_binary_sh_tpl": attr.label(
allow_single_file = True,
- default = "@rules_ruby//ruby/private:binary/binary.sh.tpl",
+ default = "@rules_ruby//ruby/private/binary:binary.sh.tpl",
+ ),
+ "_runfiles_library": attr.label(
+ allow_single_file = True,
+ default = "@bazel_tools//tools/bash/runfiles",
),
"_windows_constraint": attr.label(
default = "@platforms//os:windows",
),
}
+_EXPORT_ENV_VAR_COMMAND = "{command} {name}={value}"
+_EXPORT_BATCH_COMMAND = "set"
+_EXPORT_BASH_COMMAND = "export"
+
# buildifier: disable=function-docstring
-def generate_rb_binary_script(ctx, binary, bundler = False, args = []):
+def generate_rb_binary_script(ctx, binary, bundler = False, args = [], env = {}, java_bin = ""):
windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]
is_windows = ctx.target_platform_has_constraint(windows_constraint)
toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"]
@@ -55,10 +65,14 @@ def generate_rb_binary_script(ctx, binary, bundler = False, args = []):
if is_windows:
binary_path = binary_path.replace("/", "\\")
- toolchain_bindir = toolchain_bindir.replace("/", "\\")
+ export_command = _EXPORT_BATCH_COMMAND
+ rlocation_function = BATCH_RLOCATION_FUNCTION
script = ctx.actions.declare_file("{}.rb.cmd".format(ctx.label.name))
+ toolchain_bindir = toolchain_bindir.replace("/", "\\")
template = ctx.file._binary_cmd_tpl
else:
+ export_command = _EXPORT_BASH_COMMAND
+ rlocation_function = BASH_RLOCATION_FUNCTION
script = ctx.actions.declare_file("{}.rb.sh".format(ctx.label.name))
template = ctx.file._binary_sh_tpl
@@ -70,6 +84,11 @@ def generate_rb_binary_script(ctx, binary, bundler = False, args = []):
args = " ".join(args)
args = ctx.expand_location(args)
+ environment = []
+ for (name, value) in env.items():
+ command = _EXPORT_ENV_VAR_COMMAND.format(command = export_command, name = name, value = value)
+ environment.append(command)
+
ctx.actions.expand_template(
template = template,
output = script,
@@ -78,8 +97,11 @@ def generate_rb_binary_script(ctx, binary, bundler = False, args = []):
"{args}": args,
"{binary}": binary_path,
"{toolchain_bindir}": toolchain_bindir,
+ "{env}": "\n".join(environment),
"{bundler_command}": bundler_command,
"{ruby_binary_name}": toolchain.ruby.basename,
+ "{java_bin}": java_bin,
+ "{rlocation_function}": rlocation_function,
},
)
@@ -89,36 +111,46 @@ def generate_rb_binary_script(ctx, binary, bundler = False, args = []):
def rb_binary_impl(ctx):
bundler = False
env = {}
+ java_bin = ""
+
+ # TODO: avoid expanding the depset to a list, it may be expensive in a large graph
transitive_data = get_transitive_data(ctx.files.data, ctx.attr.deps).to_list()
transitive_deps = get_transitive_deps(ctx.attr.deps).to_list()
transitive_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps).to_list()
- tools = []
- java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"]
- ruby_toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"]
- if not ctx.attr.main:
- tools.append(ruby_toolchain.ruby)
+ ruby_toolchain = ctx.toolchains["@rules_ruby//ruby:toolchain_type"]
+ tools = [ruby_toolchain.ruby, ruby_toolchain.bundle, ruby_toolchain.gem]
if ruby_toolchain.version.startswith("jruby"):
- env["JAVA_HOME"] = java_toolchain.java_runtime.java_home_runfiles_path
+ java_toolchain = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"]
+ tools.extend(ctx.files._runfiles_library)
tools.extend(java_toolchain.java_runtime.files.to_list())
+ java_bin = java_toolchain.java_runtime.java_executable_runfiles_path[3:]
for dep in transitive_deps:
# TODO: Do not depend on workspace name to determine bundle
if dep.label.workspace_name.endswith("bundle"):
bundler = True
- runfiles = ctx.runfiles(transitive_data + transitive_srcs + tools)
-
bundle_env = get_bundle_env(ctx.attr.env, ctx.attr.deps)
env.update(bundle_env)
env.update(ctx.attr.env)
- script = generate_rb_binary_script(ctx, ctx.executable.main, bundler)
+ script = generate_rb_binary_script(
+ ctx,
+ ctx.executable.main,
+ bundler = bundler,
+ env = env,
+ java_bin = java_bin,
+ )
+
+ runfiles = ctx.runfiles(transitive_srcs + transitive_data + tools)
+ runfiles = get_transitive_runfiles(runfiles, ctx.attr.srcs, ctx.attr.deps, ctx.attr.data)
return [
DefaultInfo(
executable = script,
+ files = depset(transitive_srcs + transitive_data + tools),
runfiles = runfiles,
),
RubyFilesInfo(
diff --git a/ruby/private/binary/BUILD b/ruby/private/binary/BUILD
new file mode 100644
index 00000000..4f09aefc
--- /dev/null
+++ b/ruby/private/binary/BUILD
@@ -0,0 +1,9 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+exports_files(glob(["*.tpl"]))
+
+bzl_library(
+ name = "rlocation",
+ srcs = ["rlocation.bzl"],
+ visibility = ["//ruby:__subpackages__"],
+)
diff --git a/ruby/private/binary/binary.cmd.tpl b/ruby/private/binary/binary.cmd.tpl
index 757686b1..98a7f4c8 100644
--- a/ruby/private/binary/binary.cmd.tpl
+++ b/ruby/private/binary/binary.cmd.tpl
@@ -1,2 +1,16 @@
-@set PATH={toolchain_bindir};%PATH%
-@{bundler_command} {ruby_binary_name} {binary} {args} %*
+@echo off
+setlocal enableextensions enabledelayedexpansion
+
+:: Find location of JAVA_HOME in runfiles.
+if "{java_bin}" neq "" (
+ {rlocation_function}
+ set RUNFILES_MANIFEST_ONLY=1
+ call :rlocation {java_bin} java_bin
+ for %%a in ("%java_bin%\..\..") do set JAVA_HOME=%%~fa
+)
+
+:: Set environment variables.
+set PATH={toolchain_bindir};%PATH%
+{env}
+
+{bundler_command} {ruby_binary_name} {binary} {args} %*
diff --git a/ruby/private/binary/binary.sh.tpl b/ruby/private/binary/binary.sh.tpl
index 4603f973..ec323110 100644
--- a/ruby/private/binary/binary.sh.tpl
+++ b/ruby/private/binary/binary.sh.tpl
@@ -1,4 +1,13 @@
#!/usr/bin/env bash
+# Find location of JAVA_HOME in runfiles.
+if [ -n "{java_bin}" ]; then
+ {rlocation_function}
+ export JAVA_HOME=$(dirname $(dirname $(rlocation "{java_bin}")))
+fi
+
+# Set environment variables.
export PATH={toolchain_bindir}:$PATH
+{env}
+
{bundler_command} {ruby_binary_name} {binary} {args} $@
diff --git a/ruby/private/binary/rlocation.bzl b/ruby/private/binary/rlocation.bzl
new file mode 100644
index 00000000..1e06c37d
--- /dev/null
+++ b/ruby/private/binary/rlocation.bzl
@@ -0,0 +1,61 @@
+"Helpers to get location of files in runfiles. Vendored from aspect_bazel_lib"
+
+# https://github.com/aspect-build/bazel-lib/blob/ddac9c46c3bff4cf8d0118a164c75390dbec2da9/lib/paths.bzl
+BASH_RLOCATION_FUNCTION = r"""
+# --- begin runfiles.bash initialization v2 ---
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+source "$0.runfiles/$f" 2>/dev/null || \
+source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+"""
+
+# https://github.com/aspect-build/bazel-lib/blob/ddac9c46c3bff4cf8d0118a164c75390dbec2da9/lib/windows_utils.bzl
+BATCH_RLOCATION_FUNCTION = r"""
+rem Usage of rlocation function:
+rem call :rlocation
+rem The rlocation function maps the given to its absolute
+rem path and stores the result in a variable named .
+rem This function fails if the doesn't exist in mainifest
+rem file.
+:: Start of rlocation
+goto :rlocation_end
+:rlocation
+if "%~2" equ "" (
+ echo>&2 ERROR: Expected two arguments for rlocation function.
+ exit 1
+)
+if "%RUNFILES_MANIFEST_ONLY%" neq "1" (
+ set %~2=%~1
+ exit /b 0
+)
+if exist "%RUNFILES_DIR%" (
+ set RUNFILES_MANIFEST_FILE=%RUNFILES_DIR%_manifest
+)
+if "%RUNFILES_MANIFEST_FILE%" equ "" (
+ set RUNFILES_MANIFEST_FILE=%~f0.runfiles\MANIFEST
+)
+if not exist "%RUNFILES_MANIFEST_FILE%" (
+ set RUNFILES_MANIFEST_FILE=%~f0.runfiles_manifest
+)
+set MF=%RUNFILES_MANIFEST_FILE:/=\%
+if not exist "%MF%" (
+ echo>&2 ERROR: Manifest file %MF% does not exist.
+ exit 1
+)
+set runfile_path=%~1
+for /F "tokens=2* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /l /c:"!runfile_path! " "%MF%"`) do (
+ set abs_path=%%i
+)
+if "!abs_path!" equ "" (
+ echo>&2 ERROR: !runfile_path! not found in runfiles manifest
+ exit 1
+)
+set %~2=!abs_path!
+exit /b 0
+:rlocation_end
+:: End of rlocation
+"""
diff --git a/ruby/private/library.bzl b/ruby/private/library.bzl
index 671ad45d..82f8a6a6 100644
--- a/ruby/private/library.bzl
+++ b/ruby/private/library.bzl
@@ -6,6 +6,7 @@ load(
"get_bundle_env",
"get_transitive_data",
"get_transitive_deps",
+ "get_transitive_runfiles",
"get_transitive_srcs",
)
@@ -19,7 +20,7 @@ ATTRS = {
),
"data": attr.label_list(
allow_files = True,
- doc = "List of non-Ruby source files used to build the library.",
+ doc = "List of runtime dependencies needed by a program that depends on this library.",
),
"bundle_env": attr.string_dict(
default = {},
@@ -28,11 +29,23 @@ ATTRS = {
}
def _rb_library_impl(ctx):
+ # TODO: avoid expanding the depset to a list, it may be expensive in a large graph
+ transitive_data = get_transitive_data(ctx.files.data, ctx.attr.deps).to_list()
+ transitive_deps = get_transitive_deps(ctx.attr.deps).to_list()
+ transitive_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps).to_list()
+
+ runfiles = ctx.runfiles(transitive_srcs + transitive_data)
+ runfiles = get_transitive_runfiles(runfiles, ctx.attr.srcs, ctx.attr.deps, ctx.attr.data)
+
return [
+ DefaultInfo(
+ files = depset(transitive_srcs + transitive_data),
+ runfiles = runfiles,
+ ),
RubyFilesInfo(
- transitive_data = get_transitive_data(ctx.files.data, ctx.attr.deps),
- transitive_deps = get_transitive_deps(ctx.attr.deps),
- transitive_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps),
+ transitive_data = depset(transitive_data),
+ transitive_deps = depset(transitive_deps),
+ transitive_srcs = depset(transitive_srcs),
bundle_env = get_bundle_env(ctx.attr.bundle_env, ctx.attr.deps),
),
]
diff --git a/ruby/private/providers.bzl b/ruby/private/providers.bzl
index 38fa96fd..e4e75df0 100644
--- a/ruby/private/providers.bzl
+++ b/ruby/private/providers.bzl
@@ -47,6 +47,24 @@ def get_transitive_deps(deps):
transitive = [dep[RubyFilesInfo].transitive_deps for dep in deps],
)
+# https://bazel.build/extending/rules#runfiles
+def get_transitive_runfiles(runfiles, srcs, data, deps):
+ """Obtain the runfiles for a target, its transitive data files and dependencies.
+
+ Args:
+ runfiles: the runfiles
+ srcs: a list of source files
+ data: a list of data files
+ deps: a list of targets that are direct dependencies
+ Returns:
+ the runfiles
+ """
+ transitive_runfiles = []
+ for targets in (srcs, data, deps):
+ for target in targets:
+ transitive_runfiles.append(target[DefaultInfo].default_runfiles)
+ return runfiles.merge_all(transitive_runfiles)
+
def get_bundle_env(envs, deps):
"""Obtain the BUNDLE_* environment variables for a target and its transitive dependencies.