From 41bd35e2039471395071a063160bbcd4c4c45a15 Mon Sep 17 00:00:00 2001 From: Jeremy Nimmer Date: Thu, 12 Apr 2018 11:15:05 -0400 Subject: [PATCH] PR Defer "gurobi not found" errors until build time 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.) --- tools/workspace/gurobi/BUILD.bazel | 2 +- .../gurobi/package-macos.BUILD.bazel.in | 33 ++++++ .../gurobi/package-ubuntu.BUILD.bazel.in | 62 ++++++++++ tools/workspace/gurobi/repository.bzl | 110 ++++-------------- tools/workspace/os.bzl | 5 + 5 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 tools/workspace/gurobi/package-macos.BUILD.bazel.in create mode 100644 tools/workspace/gurobi/package-ubuntu.BUILD.bazel.in diff --git a/tools/workspace/gurobi/BUILD.bazel b/tools/workspace/gurobi/BUILD.bazel index 2e5301dc6578..0ce4c3ba02c2 100644 --- a/tools/workspace/gurobi/BUILD.bazel +++ b/tools/workspace/gurobi/BUILD.bazel @@ -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"])) diff --git a/tools/workspace/gurobi/package-macos.BUILD.bazel.in b/tools/workspace/gurobi/package-macos.BUILD.bazel.in new file mode 100644 index 000000000000..017202645bf0 --- /dev/null +++ b/tools/workspace/gurobi/package-macos.BUILD.bazel.in @@ -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") diff --git a/tools/workspace/gurobi/package-ubuntu.BUILD.bazel.in b/tools/workspace/gurobi/package-ubuntu.BUILD.bazel.in new file mode 100644 index 000000000000..146955fe6050 --- /dev/null +++ b/tools/workspace/gurobi/package-ubuntu.BUILD.bazel.in @@ -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"], +) diff --git a/tools/workspace/gurobi/repository.bzl b/tools/workspace/gurobi/repository.bzl index 8b484c1d5eb8..c6944626eb5e 100644 --- a/tools/workspace/gurobi/repository.bzl +++ b/tools/workspace/gurobi/repository.bzl @@ -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"], diff --git a/tools/workspace/os.bzl b/tools/workspace/os.bzl index a6062c86bc6a..8197d5849354 100644 --- a/tools/workspace/os.bzl +++ b/tools/workspace/os.bzl @@ -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, @@ -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