Skip to content

Commit

Permalink
More efficient r_library rule
Browse files Browse the repository at this point in the history
- Does not create an intermediate tar unless explicitly targetted.
- Does not install from binary archive, instead simply copies the files.
  • Loading branch information
siddharthab committed Oct 20, 2017
1 parent aaf2e1a commit 852bf43
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 44 deletions.
67 changes: 39 additions & 28 deletions R/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -484,29 +484,32 @@ r_pkg_test = rule(
)


def _library_impl(ctx):
library_deps = _library_deps(ctx.attr.pkgs)
bin_archive_files = []
for bin_archive in library_deps["bin_archives"]:
bin_archive_files += [bin_archive.path]
all_deps = library_deps["bin_archives"]

library_archive = ctx.actions.declare_file("library.tar")
def _library_tar_impl(ctx):
library_deps = _library_deps(ctx.attr.pkgs, path_prefix=(ctx.bin_dir.path + "/"))
command = "\n".join([
"#!/bin/bash",
"set -euo pipefail",
"",
"BIN_ARCHIVES=(",
] + bin_archive_files + [
")",
"LIBRARY_DIR=$(mktemp -d)",
_R + "CMD INSTALL --library=${LIBRARY_DIR} ${BIN_ARCHIVES[*]} > /dev/null",
"tar -c -C ${LIBRARY_DIR} -f %s ." % library_archive.path,
"rm -rf ${LIBRARY_DIR}"
])
ctx.actions.run_shell(outputs=[library_archive], inputs=all_deps,
"R_LIBS_USER=$(mktemp -d)",
library_deps["symlinked_library_command"],
"",
"TAR_TRANSFORM_OPT=\"--transform s|\.|%s|\"" % ctx.attr.tar_dir,
"if [[ $(uname -s) == \"Darwin\" ]]; then",
" TAR_TRANSFORM_OPT=\"-s |\.|%s|\"" % ctx.attr.tar_dir,
"fi",
"",
"tar -c -h -C ${{R_LIBS_USER}} -f %s ${{TAR_TRANSFORM_OPT}} ." % ctx.outputs.tar.path,
"rm -rf ${{R_LIBS_USER}}"
]).format() # symlinked_library_command assumed formatted string.
ctx.actions.run_shell(outputs=[ctx.outputs.tar], inputs=library_deps["lib_files"],
command=command)
return


def _library_impl(ctx):
_library_tar_impl(ctx)

library_deps = _library_deps(ctx.attr.pkgs)
script = "\n".join([
"#!/bin/bash",
"set -euo pipefail",
Expand All @@ -529,7 +532,7 @@ def _library_impl(ctx):
" LIBRARY_PATH=${2}; shift;",
" shift;;",
" -s)",
" SOFT_INSTALL=1; BAZEL_BIN=${2}/bazel-bin; shift;",
" SOFT_INSTALL=1; BIN_DIR=${2}/bazel-bin; shift;",
" shift;;",
" esac",
"done",
Expand All @@ -542,24 +545,25 @@ def _library_impl(ctx):
] + library_deps["lib_search_path"] + [
")",
"if (( ${SOFT_INSTALL} )); then",
" echo \"Installing package symlinks from ${BAZEL_BIN} to ${LIBRARY_PATH}\"",
" for LIB_DIR in ${BAZEL_LIB_DIRS[*]}; do",
" for PKG in ${BAZEL_BIN}/${LIB_DIR}/*; do",
" ln -s -f ${PKG} ${LIBRARY_PATH}",
" done",
" done",
" echo \"Installing package symlinks from ${BIN_DIR} to ${LIBRARY_PATH}\"",
" CMD=\"ln -s -f\"",
"else",
" echo \"Copying installed packages to ${LIBRARY_PATH}\"",
" tar -x -C ${LIBRARY_PATH} -f %s" % library_archive.short_path,
" BIN_DIR=\".\"",
" CMD=\"cp -R -L -f\"",
"fi",
"for LIB_DIR in ${BAZEL_LIB_DIRS[*]}; do",
" for PKG in ${BIN_DIR}/${LIB_DIR}/*; do",
" ${CMD} ${PKG} \"${LIBRARY_PATH}\"",
" done",
"done",
])

ctx.actions.write(
output=ctx.outputs.executable,
content=script)

runfiles = ctx.runfiles(files=([library_archive]))
return [DefaultInfo(runfiles=runfiles, files=depset([library_archive]))]
runfiles = ctx.runfiles(files=library_deps["lib_files"])
return [DefaultInfo(runfiles=runfiles, files=depset([ctx.outputs.executable]))]


r_library = rule(
Expand All @@ -573,8 +577,15 @@ r_library = rule(
doc=("If different from system default, default library " +
"location for installation. For runtime overrides, " +
"use bazel run [target] -- -l [path]")),
"tar_dir": attr.string(
default=".",
doc=("Subdirectory within the tarball where all the " +
"packages are installed")),
},
executable=True,
outputs = {
"tar": "%{name}.tar",
},
doc=("Rule to install the given package and all dependencies to " +
"a user provided or system default R library site.")
)
Expand Down
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,31 +116,35 @@ You can also create Docker images of R packages using Bazel.
In your `WORKSPACE` file, load the Docker rules and specify the base R image.

```python
git_repository(
# Change to the version of these rules you want and use sha256.
http_archive(
name = "io_bazel_rules_docker",
remote = "https://github.com/bazelbuild/rules_docker.git",
tag = "v0.1.0",
strip_prefix = "rules_docker-v0.3.0",
urls = ["https://github.com/bazelbuild/rules_docker/archive/v0.3.0.tar.gz"],
)

load(
"@io_bazel_rules_docker//docker:docker.bzl",
"docker_repositories",
"docker_pull",
"@io_bazel_rules_docker//container:container.bzl",
"container_pull",
container_repositories = "repositories",
)
docker_repositories()

docker_pull(
name = "r",
container_repositories()

container_pull(
name = "r_base",
registry = "index.docker.io",
repository = "rocker/r-ver",
repository = "rocker/r-base",
tag = "latest",
)
```

And then, in a `BUILD` file, define your library of R packages and install them
in a Docker image. Dependencies are installed implicitly.

```
```python
load("@com_grail_rules_r//R:defs.bzl", "r_library")

r_library(
name = "my_r_library",
pkgs = [
Expand All @@ -149,14 +153,15 @@ r_library(
],
)

docker_build(
name = "dev",
base = "@r//image",
load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
name = "image",
base = "@r_base//image",
directory = "/r-libs",
env = {"R_LIBS_USER": "/r-libs"},
tars = [":my_r_library"],
tars = [":my_r_library.tar"],
repository = "my_repo",
cmd = ["R"]
)
```

Expand Down
33 changes: 33 additions & 0 deletions tests/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2017 GRAIL, Inc.
#
# 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
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@com_grail_rules_r//R:defs.bzl", "r_library")

r_library(
name = "library",
pkgs = [
"//exampleC",
"@R_bitops//:bitops",
],
)

load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
name = "image",
base = "@r_base//image",
directory = "/r-libs",
env = {"R_LIBS_USER": "/r-libs"},
tars = [":library.tar"],
)
22 changes: 22 additions & 0 deletions tests/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ http_archive(
urls = ["https://github.com/google/protobuf/archive/v3.4.0.tar.gz"],
)

# Check against the master branch to pick up latest developments.
http_archive(
name = "io_bazel_rules_docker",
strip_prefix = "rules_docker-master",
urls = ["https://github.com/bazelbuild/rules_docker/archive/master.tar.gz"],
)

load(
"@io_bazel_rules_docker//container:container.bzl",
"container_pull",
container_repositories = "repositories",
)

container_repositories()

container_pull(
name = "r_base",
registry = "index.docker.io",
repository = "rocker/r-base",
tag = "latest",
)

new_http_archive(
name = "R_bitops",
build_file = "cran/BUILD.bitops",
Expand Down
2 changes: 2 additions & 0 deletions tests/exampleC/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

load("@com_grail_rules_r//R:defs.bzl", "r_package_with_test")

package(default_visibility = ["//visibility:public"])

PKG_NAME = "exampleC"

PKG_SRCS = glob(
Expand Down

0 comments on commit 852bf43

Please sign in to comment.