Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve runfiles handling #38

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions docs/rules.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions examples/gem/lib/gem/BUILD
Original file line number Diff line number Diff line change
@@ -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__"])
Expand Down Expand Up @@ -32,3 +33,15 @@ rb_binary(
args = ["lib/gem/version.rb"],
deps = [":version"],
)

run_binary(
name = "perform-addition",
srcs = [":add"],
outs = ["addition-result"],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we should have some test in this package to assert that the out file has expected content

args = [
"1",
"2",
"$(location :addition-result)",
],
tool = ":add-numbers",
)
10 changes: 8 additions & 2 deletions examples/gem/lib/gem/add.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 2 additions & 7 deletions ruby/private/BUILD
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -15,6 +9,7 @@ bzl_library(
deps = [
":library",
":providers",
"//ruby/private/binary:rlocation",
],
)

Expand Down
58 changes: 45 additions & 13 deletions ruby/private/binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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"]
Expand All @@ -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

Expand All @@ -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,
Expand All @@ -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,
},
)

Expand All @@ -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(
Expand Down
9 changes: 9 additions & 0 deletions ruby/private/binary/BUILD
Original file line number Diff line number Diff line change
@@ -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__"],
)
18 changes: 16 additions & 2 deletions ruby/private/binary/binary.cmd.tpl
Original file line number Diff line number Diff line change
@@ -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} %*
9 changes: 9 additions & 0 deletions ruby/private/binary/binary.sh.tpl
Original file line number Diff line number Diff line change
@@ -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} $@
61 changes: 61 additions & 0 deletions ruby/private/binary/rlocation.bzl
Original file line number Diff line number Diff line change
@@ -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 <runfile_path> <abs_path>
rem The rlocation function maps the given <runfile_path> to its absolute
rem path and stores the result in a variable named <abs_path>.
rem This function fails if the <runfile_path> 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
"""
Loading