Skip to content

Commit

Permalink
PR Defer "gurobi not found" errors until build time
Browse files Browse the repository at this point in the history
This is required for `bazel query` commands or `genquery()` rules to run
without error.  We want users without Gurobi configured to still be able
to use the query features of Bazel.  (This will soon become required for
linters that inspect libdrake.so compositional correctness.)
  • Loading branch information
jwnimmer-tri committed Apr 12, 2018
1 parent d981bd3 commit 41bd35e
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 88 deletions.
2 changes: 1 addition & 1 deletion tools/workspace/gurobi/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

load("//tools/lint:lint.bzl", "add_lint_tests")

add_lint_tests()
add_lint_tests(bazel_lint_extra_srcs = glob(["*.BUILD.bazel.in"]))
33 changes: 33 additions & 0 deletions tools/workspace/gurobi/package-macos.BUILD.bazel.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- python -*-

load("@drake//tools/install:install.bzl", "install")

licenses(["by_exception_only"]) # Gurobi

# This rule is only built if a glob() call fails.
genrule(
name = "error-message",
outs = ["error-message.h"],
cmd = "echo 'error: Gurobi 7.5.2 is not installed at {gurobi_path}' && false", # noqa
visibility = ["//visibility:private"],
)

GUROBI_HDRS = glob([
"gurobi-distro/include/gurobi_c.h",
"gurobi-distro/include/gurobi_c++.h",
]) or [":error-message.h"]

cc_library(
name = "gurobi",
hdrs = GUROBI_HDRS,
includes = ["gurobi-distro/include"],
linkopts = [
"-L{gurobi_path}/lib",
"-lgurobi75",
],
visibility = ["//visibility:public"],
)

# For macOS, the Drake install step does not need any additional actions to
# install Gurobi, since Gurobi was already installed system-wide in /Library.
install(name = "install")
62 changes: 62 additions & 0 deletions tools/workspace/gurobi/package-ubuntu.BUILD.bazel.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- python -*-

load("@drake//tools/install:install.bzl", "install", "install_files")

licenses(["by_exception_only"]) # Gurobi

# This rule is only built if a glob() call fails.
genrule(
name = "error-message",
outs = ["error-message.h"],
cmd = "echo 'error: The value of environment variable GUROBI_PATH=\"{gurobi_path}\" is invalid; export GUROBI_PATH to the correct value.' && false", # noqa
visibility = ["//visibility:private"],
)

GUROBI_HDRS = glob([
"gurobi-distro/include/gurobi_c.h",
"gurobi-distro/include/gurobi_c++.h",
]) or [":error-message.h"]

# In the Gurobi package, libgurobi75.so is a symlink to libgurobi.so.7.5.2.
# However, if we use libgurobi.so.7.5.2 in srcs, executables that link this
# library will be unable to find it at runtime in the Bazel sandbox,
# because the NEEDED statements in the executable will not square with the
# RPATH statements. I don't really know why this happens, but I suspect it
# might be a Bazel bug.
GUROBI_SRCS = glob([
"gurobi-distro/lib/libgurobi75.so",
]) or [":error-message.h"]

GUROBI_INSTALL_LIBRARIES = glob([
"gurobi-distro/lib/libgurobi.so.7.5.2",
"gurobi-distro/lib/libgurobi75.so",
]) or [":error-message.h"]

GUROBI_DOCS = glob([
"gurobi-distro/EULA.pdf",
]) or [":error-message.h"]

cc_library(
name = "gurobi",
srcs = GUROBI_SRCS,
hdrs = GUROBI_HDRS,
includes = ["gurobi-distro/include"],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
)

install_files(
name = "install_libraries",
dest = ".",
files = GUROBI_INSTALL_LIBRARIES,
strip_prefix = ["gurobi-distro"],
visibility = ["//visibility:private"],
)

install(
name = "install",
docs = GUROBI_DOCS,
doc_strip_prefix = ["gurobi-distro"],
visibility = ["//visibility:public"],
deps = [":install_libraries"],
)
110 changes: 23 additions & 87 deletions tools/workspace/gurobi/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,35 @@ load("@drake//tools/workspace:os.bzl", "determine_os")

# Ubuntu only: GUROBI_PATH should be the linux64 directory in the Gurobi 7.5.2
# release.
def _gurobi_impl(repository_ctx):
os_result = determine_os(repository_ctx)
#
# TODO(jwnimmer-tri) The Gurobi docs use /opt/gurobi752/linux64, so we should
# probably look in that location as a reasonable default guess.
def _gurobi_impl(repo_ctx):
os_result = determine_os(repo_ctx)
if os_result.error != None:
fail(os_result.error)

if os_result.is_macos:
# Gurobi must be installed into its standard location.
gurobi_path = "/Library/gurobi752/mac64"
repository_ctx.symlink(gurobi_path, "gurobi-distro")
warning = "Gurobi 7.5.2 is not installed."
srcs = []

lib_path = repository_ctx.path("gurobi-distro/lib")
linkopts = [
"-L{}".format(lib_path),
"-lgurobi75",
]
else:
gurobi_path = repository_ctx.os.environ.get("GUROBI_PATH", "")
repository_ctx.symlink(
gurobi_path or "/MISSING_GUROBI_PATH",
"gurobi-distro")

if not gurobi_path:
warning_detail = "GUROBI_PATH is empty or unset"
else:
warning_detail = "GUROBI_PATH=%s is invalid" % gurobi_path
warning = (
"gurobi.bzl: The saved value of " + warning_detail + "; " +
"export GUROBI_PATH to the correct value.")

# In the Gurobi package, libgurobi75.so is just a symlink to
# libgurobi.so.7.5.2. However, if you use libgurobi.so.7.5.2 in srcs,
# executables that link this library will be unable to find it at
# runtime in the Bazel sandbox, because the NEEDED statements in the
# executable will not square with the RPATH statements. I don't really
# know why this happens, but I suspect it might be a Bazel bug.
srcs = ["gurobi-distro/lib/libgurobi75.so"]

linkopts = ["-pthread"]

file_content = """# -*- python -*-
# DO NOT EDIT: generated by gurobi_repository()
licenses(["by_exception_only"]) # Gurobi
package(default_visibility = ["//visibility:public"])
GUROBI_HDRS = glob([
"gurobi-distro/include/gurobi_c.h",
"gurobi-distro/include/gurobi_c++.h",
])
print("{warning}") if not GUROBI_HDRS else cc_library(
name = "gurobi",
srcs = {srcs},
hdrs = GUROBI_HDRS,
includes = ["gurobi-distro/include"],
linkopts = {linkopts},
)
""".format(warning = warning, srcs = srcs, linkopts = linkopts)

if os_result.is_macos:
file_content += """
load("@drake//tools/install:install.bzl", "install")
install(name = "install")
"""
repo_ctx.symlink(gurobi_path, "gurobi-distro")
else:
file_content += """
load("@drake//tools/install:install.bzl", "install", "install_files")
install_files(
name = "install_libraries",
dest = ".",
files = [
"gurobi-distro/lib/libgurobi.so.7.5.2",
"gurobi-distro/lib/libgurobi75.so",
],
strip_prefix = ["gurobi-distro"],
visibility = ["//visibility:private"],
)
install(
name = "install",
docs = ["gurobi-distro/EULA.pdf"],
doc_strip_prefix = ["gurobi-distro"],
deps = [":install_libraries"],
)
"""

repository_ctx.file("BUILD.bazel", content = file_content,
executable = False)
# Locate Gurobi using an environment variable. If GUROBI_PATH is
# unset, pass the empty string to our template() call, but symlink a
# dummy distro path since we can't symlink to the empty string.
gurobi_path = repo_ctx.os.environ.get("GUROBI_PATH", "")
repo_ctx.symlink(gurobi_path or "/no-gurobi-path", "gurobi-distro")

# Emit the generated BUILD.bazel file.
repo_ctx.template(
"BUILD.bazel",
Label("@drake//tools/workspace/gurobi:" +
"package-{}.BUILD.bazel.in".format(os_result.distribution)),
substitutions = {
"{gurobi_path}": gurobi_path,
},
executable = False,
)

gurobi_repository = repository_rule(
environ = ["GUROBI_PATH"],
Expand Down
5 changes: 5 additions & 0 deletions tools/workspace/os.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def _make_result(error = None,
"""Return a fully-populated struct result for determine_os, below."""
return struct(
error = error,
distribution = (
"ubuntu" if (ubuntu_release != None) else
"macos" if (macos_release != None) else
None),
is_macos = (macos_release != None),
is_ubuntu = (ubuntu_release != None),
ubuntu_release = ubuntu_release,
Expand Down Expand Up @@ -109,6 +113,7 @@ def determine_os(repository_ctx):
Result:
a struct, with attributes:
- error: str iff any error occurred, else None
- distribution: str either "ubuntu" or "macos" if no error
- is_macos: True iff on a supported macOS release, else False
- macos_release: str like "10.13" iff on a supported macOS, else None
- is_ubuntu: True iff on a supported Ubuntu version, else False
Expand Down

0 comments on commit 41bd35e

Please sign in to comment.