From 85151ef06051e982885b9b3537fed4e6e07462fe Mon Sep 17 00:00:00 2001 From: Jeremy Nimmer Date: Thu, 2 Jun 2022 14:23:40 -0700 Subject: [PATCH] [workspace] Build NLopt from source Compared to the Ubuntu and macOS packages, this removes the LGPL'd "luksan" solver from the build, which means we can link the library statically (and eventually, hidden). This might mean that NLopt is no longer able to solve certain kinds of programs, but that is a fair trade-off given all of the problems caused by LGPL linking. The package.BUILD.bazel is modeled on nlopt.BUILD.bazel in a10ebb7. Deprecate the now-unused current (host OS) nlopt external. --- solvers/BUILD.bazel | 2 +- tools/install/libdrake/BUILD.bazel | 13 - tools/wheel/image/packages-focal | 2 - tools/workspace/BUILD.bazel | 1 + tools/workspace/default.bzl | 5 + .../dreal/{ => patches}/ibex_2.8.6.patch | 0 tools/workspace/dreal/patches/warnings.patch | 12 + tools/workspace/dreal/repository.bzl | 6 +- .../workspace/nlopt/package-macos.BUILD.bazel | 1 + .../nlopt/package-ubuntu-20.04.BUILD.bazel | 1 + tools/workspace/nlopt_internal/BUILD.bazel | 45 +++ .../nlopt_internal/package.BUILD.bazel | 347 ++++++++++++++++++ .../nlopt_internal/patches/gen_enums.patch | 75 ++++ .../patches/remove_luksan.patch | 39 ++ .../nlopt_internal/patches/vendoring.patch | 24 ++ tools/workspace/nlopt_internal/repository.bzl | 19 + .../nlopt_internal/test/enum_test.py | 42 +++ tools/workspace/vendor_cxx.py | 4 + tools/workspace/vendor_cxx_test.py | 18 + 19 files changed, 639 insertions(+), 17 deletions(-) rename tools/workspace/dreal/{ => patches}/ibex_2.8.6.patch (100%) create mode 100644 tools/workspace/dreal/patches/warnings.patch create mode 100644 tools/workspace/nlopt_internal/BUILD.bazel create mode 100644 tools/workspace/nlopt_internal/package.BUILD.bazel create mode 100644 tools/workspace/nlopt_internal/patches/gen_enums.patch create mode 100644 tools/workspace/nlopt_internal/patches/remove_luksan.patch create mode 100644 tools/workspace/nlopt_internal/patches/vendoring.patch create mode 100644 tools/workspace/nlopt_internal/repository.bzl create mode 100644 tools/workspace/nlopt_internal/test/enum_test.py diff --git a/solvers/BUILD.bazel b/solvers/BUILD.bazel index 0a0363320b3a..4abf996b9702 100644 --- a/solvers/BUILD.bazel +++ b/solvers/BUILD.bazel @@ -975,7 +975,7 @@ drake_cc_variant_library( ], deps_enabled = [ "//math:autodiff", - "@nlopt", + "@nlopt_internal//:nlopt", ], ) diff --git a/tools/install/libdrake/BUILD.bazel b/tools/install/libdrake/BUILD.bazel index a04f6b9920a4..cb2a26a107bd 100644 --- a/tools/install/libdrake/BUILD.bazel +++ b/tools/install/libdrake/BUILD.bazel @@ -126,7 +126,6 @@ cc_library( name = "dreal_deps", deps = select({ "//conditions:default": [ - "@nlopt", "@ibex", ], "//tools:no_dreal": [], @@ -162,17 +161,6 @@ cc_library( }), ) -# Depend on NLOPT's shared library iff NLOPT is enabled. -cc_library( - name = "nlopt_deps", - deps = select({ - "//conditions:default": [ - "@nlopt", - ], - "//tools:no_nlopt": [], - }), -) - # Depend on the subset of VTK's shared libraries that Drake uses. cc_library( name = "vtk_deps", @@ -239,7 +227,6 @@ cc_library( ":gurobi_deps", ":ipopt_deps", ":mosek_deps", - ":nlopt_deps", ":vtk_deps", ":x11_deps", "//common:drake_marker_shared_library", diff --git a/tools/wheel/image/packages-focal b/tools/wheel/image/packages-focal index 5d3fb5594121..729387c95c4c 100644 --- a/tools/wheel/image/packages-focal +++ b/tools/wheel/image/packages-focal @@ -31,8 +31,6 @@ zip # Build dependencies (general). libgl1-mesa-dev libglib2.0-dev -libnlopt-dev -libnlopt-cxx-dev libxt-dev # Build dependencies (OpenCL). opencl-headers diff --git a/tools/workspace/BUILD.bazel b/tools/workspace/BUILD.bazel index 27f05d04e10d..269c7eb7529f 100644 --- a/tools/workspace/BUILD.bazel +++ b/tools/workspace/BUILD.bazel @@ -49,6 +49,7 @@ drake_py_binary( srcs = ["vendor_cxx.py"], visibility = [ # These should all be of the form "@foo_internal//:__pkg__". + "@nlopt_internal//:__pkg__", "@qhull_internal//:__pkg__", "@yaml_cpp_internal//:__pkg__", ], diff --git a/tools/workspace/default.bzl b/tools/workspace/default.bzl index 3297231bbc26..49a33d5e9049 100644 --- a/tools/workspace/default.bzl +++ b/tools/workspace/default.bzl @@ -61,6 +61,7 @@ load("@drake//tools/workspace/mosek:repository.bzl", "mosek_repository") load("@drake//tools/workspace/msgpack:repository.bzl", "msgpack_repository") load("@drake//tools/workspace/net_sf_jchart2d:repository.bzl", "net_sf_jchart2d_repository") # noqa load("@drake//tools/workspace/nlopt:repository.bzl", "nlopt_repository") +load("@drake//tools/workspace/nlopt_internal:repository.bzl", "nlopt_internal_repository") # noqa load("@drake//tools/workspace/openblas:repository.bzl", "openblas_repository") load("@drake//tools/workspace/opencl:repository.bzl", "opencl_repository") load("@drake//tools/workspace/opengl:repository.bzl", "opengl_repository") @@ -238,7 +239,11 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS): if "net_sf_jchart2d" not in excludes: net_sf_jchart2d_repository(name = "net_sf_jchart2d", mirrors = mirrors) if "nlopt" not in excludes: + # The @nlopt external is being removed from Drake on 2020-09-01. + # TODO(jwnimmer-tri) When removing @nlopt, also update install_prereqs. nlopt_repository(name = "nlopt") + if "nlopt_internal" not in excludes: + nlopt_internal_repository(name = "nlopt_internal", mirrors = mirrors) if "openblas" not in excludes: openblas_repository(name = "openblas") if "opencl" not in excludes: diff --git a/tools/workspace/dreal/ibex_2.8.6.patch b/tools/workspace/dreal/patches/ibex_2.8.6.patch similarity index 100% rename from tools/workspace/dreal/ibex_2.8.6.patch rename to tools/workspace/dreal/patches/ibex_2.8.6.patch diff --git a/tools/workspace/dreal/patches/warnings.patch b/tools/workspace/dreal/patches/warnings.patch new file mode 100644 index 000000000000..227e5a684133 --- /dev/null +++ b/tools/workspace/dreal/patches/warnings.patch @@ -0,0 +1,12 @@ +TBD + +--- tools/dreal.bzl ++++ tools/dreal.bzl +@@ -25,6 +25,7 @@ + "-Woverloaded-virtual", + "-Wpedantic", + "-Wshadow", ++ "-Wno-attributes", + ] + + # The CLANG_FLAGS will be enabled for all C++ rules in the project when diff --git a/tools/workspace/dreal/repository.bzl b/tools/workspace/dreal/repository.bzl index d8af56955393..61b2f61a4b15 100644 --- a/tools/workspace/dreal/repository.bzl +++ b/tools/workspace/dreal/repository.bzl @@ -12,6 +12,10 @@ def dreal_repository( sha256 = "7bbd328a25c14cff814753694b1823257bb7cff7f84a7b705b9f111624d5b2e4", # noqa mirrors = mirrors, patches = [ - "@drake//tools/workspace/dreal:ibex_2.8.6.patch", + "@drake//tools/workspace/dreal:patches/ibex_2.8.6.patch", + "@drake//tools/workspace/dreal:patches/warnings.patch", ], + repo_mapping = { + "@nlopt": "@nlopt_internal", + }, ) diff --git a/tools/workspace/nlopt/package-macos.BUILD.bazel b/tools/workspace/nlopt/package-macos.BUILD.bazel index 644009fd0b3a..f4106b631f09 100644 --- a/tools/workspace/nlopt/package-macos.BUILD.bazel +++ b/tools/workspace/nlopt/package-macos.BUILD.bazel @@ -21,4 +21,5 @@ cc_library( "-lnlopt", ], visibility = ["//visibility:public"], + deprecation = "DRAKE DEPRECATED: The @nlopt external is being removed from Drake on or after 2020-09-01. Downstream projects should add it to their own WORKSPACE if needed.", # noqa ) diff --git a/tools/workspace/nlopt/package-ubuntu-20.04.BUILD.bazel b/tools/workspace/nlopt/package-ubuntu-20.04.BUILD.bazel index b5d14f776188..ef69027ee1a8 100644 --- a/tools/workspace/nlopt/package-ubuntu-20.04.BUILD.bazel +++ b/tools/workspace/nlopt/package-ubuntu-20.04.BUILD.bazel @@ -15,4 +15,5 @@ cc_library( "-lnlopt_cxx", ], visibility = ["//visibility:public"], + deprecation = "DRAKE DEPRECATED: The @nlopt external is being removed from Drake on or after 2020-09-01. Downstream projects should add it to their own WORKSPACE if needed.", # noqa ) diff --git a/tools/workspace/nlopt_internal/BUILD.bazel b/tools/workspace/nlopt_internal/BUILD.bazel new file mode 100644 index 000000000000..9b630784086d --- /dev/null +++ b/tools/workspace/nlopt_internal/BUILD.bazel @@ -0,0 +1,45 @@ +# -*- python -*- + +load("//tools/skylark:drake_py.bzl", "drake_py_unittest") +load("//tools/lint:lint.bzl", "add_lint_tests") + +exports_files( + ["patches/gen_enums.patch"], + visibility = ["@nlopt_internal//:__pkg__"], +) + +# Creates api/nlopt.hpp using NLopt upstream's codegen so that we can +# cross-check it versus our Drake-local patch file. +genrule( + name = "cmake_genrule", + srcs = [ + "@nlopt_internal//:cmake/generate-cpp.cmake", + "@nlopt_internal//:src/api/nlopt-in.hpp", + "@nlopt_internal//:src/api/nlopt.h", + ], + outs = ["test/nlopt-upstream.hpp"], + cmd = " ".join([ + "cmake", + "-DAPI_SOURCE_DIR=$$(dirname $(execpath @nlopt_internal//:src/api/nlopt.h))", # noqa + "-P $(execpath @nlopt_internal//:cmake/generate-cpp.cmake)", + "&&", + "mv nlopt.hpp $@", + ]), + tags = [ + "manual", + "nolint", + ], +) + +drake_py_unittest( + name = "enum_test", + data = [ + ":test/nlopt-upstream.hpp", + "@nlopt_internal//:genrule/nlopt.hpp", + ], + deps = [ + "@bazel_tools//tools/python/runfiles", + ], +) + +add_lint_tests() diff --git a/tools/workspace/nlopt_internal/package.BUILD.bazel b/tools/workspace/nlopt_internal/package.BUILD.bazel new file mode 100644 index 000000000000..ed9d5ad1cf7c --- /dev/null +++ b/tools/workspace/nlopt_internal/package.BUILD.bazel @@ -0,0 +1,347 @@ +# -*- python -*- + +load( + "@drake//tools/workspace:cmake_configure_file.bzl", + "cmake_configure_file", +) +load( + "@drake//tools/install:install.bzl", + "install", +) +load( + "@drake//tools/workspace:check_lists_consistency.bzl", + "check_lists_consistency", +) +load( + "@drake//tools/workspace:vendor_cxx.bzl", + "cc_library_vendored", +) + +package(default_visibility = ["//visibility:private"]) + +# Configure the NLopt preprocessor substitutions. +cmake_configure_file( + name = "config_genrule", + src = "nlopt_config.h.in", + out = "src/nlopt_config.h", + cmakelists = ["CMakeLists.txt"], + defines = [ + # Yes, we are going to build the C++ bindings. + "NLOPT_CXX=1", + # C11 standard spelling. + "THREADLOCAL=_Thread_local", + # Say "yes" to some useful things. + "HAVE_COPYSIGN=1", + "HAVE_FPCLASSIFY=1", + "HAVE_ISINF=1", + "HAVE_ISNAN=1", + "HAVE_LIBM=1", + "HAVE_QSORT_R=1", + "HAVE_STDINT_H=1", + "HAVE_UINT32_T=1", + # These end up being unused; empty-string is a fail-fast value. + "SIZEOF_UNSIGNED_INT=", + "SIZEOF_UNSIGNED_LONG=", + ], +) + +# Make the config header available as a private library. +cc_library( + name = "config", + hdrs = [":src/nlopt_config.h"], + includes = ["src"], + linkstatic = True, +) + +# The _SRCS_UTIL and _HDRS_UTIL cover the subset of NLOPT_SOURCES (from the +# upstream CMakeLists.txt) that live in the "util" folder. These files are +# compiled as C code (not C++). +_SRCS_UTIL = [ + "src/util/mt19937ar.c", + "src/util/qsort_r.c", + "src/util/redblack.c", + "src/util/rescale.c", + "src/util/soboldata.h", + "src/util/sobolseq.c", + "src/util/stop.c", + "src/util/timer.c", +] + +_HDRS_UTIL = [ + "src/util/redblack.h", + "src/util/nlopt-util.h", +] + +cc_library( + name = "util", + srcs = _SRCS_UTIL, + hdrs = _HDRS_UTIL, + copts = ["-w", "-fvisibility=hidden"], + includes = ["src/util"], + linkstatic = True, + deps = [ + ":config", + ], +) + +# The _SRCS_ALGS_C and _HDRS_ALGS_C cover the subset of NLOPT_SOURCES (from the +# upstream CMakeLists.txt) that live in the "algs" folder, with the exception +# of "src/algs/luksan/**" because it is licensed under LGPL-2.1+. +_SRCS_ALGS_C = [ + "src/algs/auglag/auglag.c", + "src/algs/bobyqa/bobyqa.c", + "src/algs/cdirect/cdirect.c", + "src/algs/cdirect/hybrid.c", + "src/algs/cobyla/cobyla.c", + "src/algs/crs/crs.c", + "src/algs/direct/DIRect.c", + "src/algs/direct/DIRserial.c", + "src/algs/direct/DIRsubrout.c", + "src/algs/direct/direct-internal.h", + "src/algs/direct/direct_wrap.c", + "src/algs/esch/esch.c", + "src/algs/isres/isres.c", + "src/algs/mlsl/mlsl.c", + "src/algs/mma/ccsa_quadratic.c", + "src/algs/mma/mma.c", + "src/algs/neldermead/nldrmd.c", + "src/algs/neldermead/sbplx.c", + "src/algs/newuoa/newuoa.c", + "src/algs/praxis/praxis.c", + "src/algs/slsqp/slsqp.c", +] + +_HDRS_ALGS_C = [ + "src/algs/auglag/auglag.h", + "src/algs/bobyqa/bobyqa.h", + "src/algs/cdirect/cdirect.h", + "src/algs/cobyla/cobyla.h", + "src/algs/crs/crs.h", + "src/algs/direct/direct.h", + "src/algs/esch/esch.h", + "src/algs/isres/isres.h", + "src/algs/mlsl/mlsl.h", + "src/algs/mma/mma.h", + "src/algs/neldermead/neldermead.h", + "src/algs/newuoa/newuoa.h", + "src/algs/praxis/praxis.h", + "src/algs/slsqp/slsqp.h", +] + +cc_library( + name = "algs_c", + srcs = _SRCS_ALGS_C, + hdrs = _HDRS_ALGS_C, + copts = ["-w", "-fvisibility=hidden"], + includes = [ + "src/algs/auglag", + "src/algs/bobyqa", + "src/algs/cdirect", + "src/algs/cobyla", + "src/algs/crs", + "src/algs/direct", + "src/algs/esch", + "src/algs/isres", + "src/algs/mlsl", + "src/algs/mma", + "src/algs/neldermead", + "src/algs/newuoa", + "src/algs/praxis", + "src/algs/slsqp", + ], + linkstatic = True, + deps = [ + ":util", + ], +) + +# The _SRCS_STOGO and _HDRS_STOGO cover the NLOPT_CXX sources from the upstream +# CMakeLists.txt. +_SRCS_STOGO = [ + "src/algs/stogo/global.cc", + "src/algs/stogo/global.h", + "src/algs/stogo/linalg.cc", + "src/algs/stogo/linalg.h", + "src/algs/stogo/local.cc", + "src/algs/stogo/local.h", + "src/algs/stogo/stogo.cc", + "src/algs/stogo/stogo_config.h", + "src/algs/stogo/tools.cc", + "src/algs/stogo/tools.h", +] + +_HDRS_STOGO = [ + "src/algs/stogo/stogo.h", +] + +cc_library_vendored( + name = "stogo", + srcs = _SRCS_STOGO, + srcs_vendored = ["drake_" + x for x in _SRCS_STOGO], + hdrs = _HDRS_STOGO, + hdrs_vendored = ["drake_" + x for x in _HDRS_STOGO], + copts = ["-w"], + includes = ["drake_src/algs/stogo"], + linkstatic = True, + deps = [ + ":util", + ], +) + +# The _SRCS_AGS and _HDRS_AGS cover the NLOPT_CXX sources from the upstream +# CMakeLists.txt. +_SRCS_AGS = [ + "src/algs/ags/ags.cc", + "src/algs/ags/data_types.hpp", + "src/algs/ags/evolvent.cc", + "src/algs/ags/evolvent.hpp", + "src/algs/ags/local_optimizer.cc", + "src/algs/ags/local_optimizer.hpp", + "src/algs/ags/solver.cc", + "src/algs/ags/solver.hpp", +] + +_HDRS_AGS = [ + "src/algs/ags/ags.h", +] + +cc_library_vendored( + name = "ags", + srcs = _SRCS_AGS, + srcs_vendored = ["drake_" + x for x in _SRCS_AGS], + hdrs = _HDRS_AGS, + hdrs_vendored = ["drake_" + x for x in _HDRS_AGS], + copts = ["-w"], + includes = ["drake_src/algs/ags"], + linkstatic = True, + deps = [ + ":util", + ], +) + +# The _SRCS_API and _HDRS_API cover the subset of NLOPT_SOURCES (from the +# upstream CMakeLists.txt) that live in the "api" folder. These files are +# compiled as C code (not C++). +_SRCS_API = [ + "src/api/deprecated.c", + "src/api/general.c", + "src/api/nlopt-internal.h", + "src/api/optimize.c", + "src/api/options.c", +] + +_HDRS_API = [ + "src/api/nlopt.h", +] + +cc_library( + name = "api", + srcs = _SRCS_API, + hdrs = _HDRS_API, + copts = ["-w", "-fvisibility=hidden"], + includes = ["src/api"], + linkstatic = True, + deps = [ + ":ags", + ":algs_c", + ":config", + ":stogo", + ":util", + ], +) + +_ALL_SRCS = _SRCS_UTIL + _SRCS_ALGS_C + _SRCS_STOGO + _SRCS_AGS + _SRCS_API +_ALL_HDRS = _HDRS_UTIL + _HDRS_ALGS_C + _HDRS_STOGO + _HDRS_AGS + _HDRS_API + +# Fail-fast in case upstream adds/removes files as part of an upgrade. +check_lists_consistency( + files = _ALL_SRCS + _ALL_HDRS, + glob_include = [ + "src/**/*.h", + "src/**/*.c", + "src/**/*.hpp", + "src/**/*.cc", + ], + # Keep this list of excludes in sync with the install() copyright excludes + # at the bottom of this file. + glob_exclude = [ + # These are disabled upstream by default. + "src/algs/cquad/**", + "src/algs/direct/DIRparallel.c", + "src/algs/subplex/**", + # This is disabled in Drake specifically due to LGPL. + "src/algs/luksan/**", + # This is a main() function, not a library source. + "src/algs/stogo/prog.cc", + # This is used as a genrule input only, not a library source. + "src/api/nlopt-in.hpp", + # Drake doesn't use the fortran bindings. + "src/api/f77*", + # Drake doesn't use the octave bindings. + "src/octave/**", + # These are unit testing files, not library sources. + "**/*test*", + "**/*tst*", + "src/algs/stogo/rosen.h", + "src/util/nlopt-getopt.*", + ], +) + +# Create api/nlopt.hpp based on api/nlopt-in.hpp. +genrule( + name = "nlopt_hpp_genrule", + srcs = [ + "src/api/nlopt-in.hpp", + "@drake//tools/workspace/nlopt_internal:patches/gen_enums.patch", + ], + outs = ["genrule/nlopt.hpp"], + cmd = " ".join([ + "cp $(execpath src/api/nlopt-in.hpp) $@", + "&&", + "patch $@ $(execpath @drake//tools/workspace/nlopt_internal:patches/gen_enums.patch)", # noqa + ]), + visibility = ["@drake//tools/workspace/nlopt_internal:__pkg__"], +) + +# Provides upstream's inputs for nlopt.hpp for use by Drake's unit tests. +exports_files( + [ + "cmake/generate-cpp.cmake", + "src/api/nlopt-in.hpp", + "src/api/nlopt.h", + ], + visibility = ["@//tools/workspace/nlopt_internal:__pkg__"], +) + +cc_library_vendored( + name = "nlopt", + hdrs = ["genrule/nlopt.hpp"], + hdrs_vendored = ["vendored/nlopt.hpp"], + includes = ["vendored"], + linkstatic = True, + visibility = ["//visibility:public"], + deps = [ + ":api", + ], +) + +install( + name = "install", + targets = [":nlopt"], + docs = [ + "src/AUTHORS", + "src/NEWS", + ] + glob([ + "src/**/COPYING", + "src/**/COPYRIGHT", + "src/**/README", + "src/**/README.orig", + ], exclude = [ + # These are disabled upstream by default. + "src/algs/cquad/**", + "src/algs/subplex/**", + # This is disabled in Drake specifically due to LGPL. + "src/algs/luksan/**", + ]), + visibility = ["//visibility:public"], +) diff --git a/tools/workspace/nlopt_internal/patches/gen_enums.patch b/tools/workspace/nlopt_internal/patches/gen_enums.patch new file mode 100644 index 000000000000..13ee21941246 --- /dev/null +++ b/tools/workspace/nlopt_internal/patches/gen_enums.patch @@ -0,0 +1,75 @@ +NLopt uses a CMake script to copy a C enum into a C++ enum. + +Here we create the same effect by patching the source file directly. +Separately, we cross-check that this patch the CMake script produce +consistent results. + +--- src/api/nlopt-in.hpp.orig ++++ src/api/nlopt-in.hpp +@@ -39,4 +34,66 @@ + // nlopt::* namespace versions of the C enumerated types + // AUTOMATICALLY GENERATED, DO NOT EDIT + // GEN_ENUMS_HERE ++ enum algorithm { ++ GN_DIRECT = 0, ++ GN_DIRECT_L, ++ GN_DIRECT_L_RAND, ++ GN_DIRECT_NOSCAL, ++ GN_DIRECT_L_NOSCAL, ++ GN_DIRECT_L_RAND_NOSCAL, ++ GN_ORIG_DIRECT, ++ GN_ORIG_DIRECT_L, ++ GD_STOGO, ++ GD_STOGO_RAND, ++ LD_LBFGS_NOCEDAL, ++ LD_LBFGS, ++ LN_PRAXIS, ++ LD_VAR1, ++ LD_VAR2, ++ LD_TNEWTON, ++ LD_TNEWTON_RESTART, ++ LD_TNEWTON_PRECOND, ++ LD_TNEWTON_PRECOND_RESTART, ++ GN_CRS2_LM, ++ GN_MLSL, ++ GD_MLSL, ++ GN_MLSL_LDS, ++ GD_MLSL_LDS, ++ LD_MMA, ++ LN_COBYLA, ++ LN_NEWUOA, ++ LN_NEWUOA_BOUND, ++ LN_NELDERMEAD, ++ LN_SBPLX, ++ LN_AUGLAG, ++ LD_AUGLAG, ++ LN_AUGLAG_EQ, ++ LD_AUGLAG_EQ, ++ LN_BOBYQA, ++ GN_ISRES, ++ AUGLAG, ++ AUGLAG_EQ, ++ G_MLSL, ++ G_MLSL_LDS, ++ LD_SLSQP, ++ LD_CCSAQ, ++ GN_ESCH, ++ GN_AGS, ++ NUM_ALGORITHMS /* not an algorithm, just the number of them */ ++ }; ++ enum result { ++ FAILURE = -1, /* generic failure code */ ++ INVALID_ARGS = -2, ++ OUT_OF_MEMORY = -3, ++ ROUNDOFF_LIMITED = -4, ++ FORCED_STOP = -5, ++ NUM_FAILURES = -6, /* not a result, just the number of possible failures */ ++ SUCCESS = 1, /* generic success code */ ++ STOPVAL_REACHED = 2, ++ FTOL_REACHED = 3, ++ XTOL_REACHED = 4, ++ MAXEVAL_REACHED = 5, ++ MAXTIME_REACHED = 6, ++ NUM_RESULTS /* not a result, just the number of possible successes */ ++ }; + ////////////////////////////////////////////////////////////////////// diff --git a/tools/workspace/nlopt_internal/patches/remove_luksan.patch b/tools/workspace/nlopt_internal/patches/remove_luksan.patch new file mode 100644 index 000000000000..2b5eed6f9469 --- /dev/null +++ b/tools/workspace/nlopt_internal/patches/remove_luksan.patch @@ -0,0 +1,39 @@ +Remove NLopt's dependency on it's internal luksan algorithm library. + +That library is licensenced under LGPL-2.1+ but the rest of NLopt is +licensed under MIT or similar notice-only licenses, and we really +don't want to distribute this code using dynamic linking. + +--- src/api/optimize.c.orig ++++ src/api/optimize.c +@@ -40,7 +40,9 @@ + + #include "cdirect.h" + ++#if 0 + #include "luksan.h" ++#endif + + #include "crs.h" + +@@ -573,18 +575,18 @@ + return praxis_(nlopt_get_param(opt, "t0_tol", 0.0), DBL_EPSILON, step, ni, x, f_bound, opt, &stop, minf); + } + ++#if 0 + case NLOPT_LD_LBFGS: + return luksan_plis(ni, f, f_data, lb, ub, x, minf, &stop, opt->vector_storage); +- + case NLOPT_LD_VAR1: + case NLOPT_LD_VAR2: + return luksan_plip(ni, f, f_data, lb, ub, x, minf, &stop, opt->vector_storage, algorithm == NLOPT_LD_VAR1 ? 1 : 2); +- + case NLOPT_LD_TNEWTON: + case NLOPT_LD_TNEWTON_RESTART: + case NLOPT_LD_TNEWTON_PRECOND: + case NLOPT_LD_TNEWTON_PRECOND_RESTART: + return luksan_pnet(ni, f, f_data, lb, ub, x, minf, &stop, opt->vector_storage, 1 + (algorithm - NLOPT_LD_TNEWTON) % 2, 1 + (algorithm - NLOPT_LD_TNEWTON) / 2); ++#endif + + case NLOPT_GN_CRS2_LM: + if (!finite_domain(n, lb, ub)) diff --git a/tools/workspace/nlopt_internal/patches/vendoring.patch b/tools/workspace/nlopt_internal/patches/vendoring.patch new file mode 100644 index 000000000000..edad0677a3ca --- /dev/null +++ b/tools/workspace/nlopt_internal/patches/vendoring.patch @@ -0,0 +1,24 @@ +When changing the namespace of C++ code that's called from C, we need to +keep the function names that are called from C unchanged. + +--- src/algs/ags/ags.cc ++++ src/algs/ags/ags.cc +@@ -16,6 +16,7 @@ + int ags_refine_loc = 0; + int ags_verbose = 0; + ++extern "C" + int ags_minimize(unsigned n, nlopt_func func, void *data, unsigned m, nlopt_constraint *fc, + double *x, double *minf, const double *l, const double *u, nlopt_stopping *stop) + { +--- src/algs/stogo/stogo.cc ++++ src/algs/stogo/stogo.cc +@@ -26,7 +26,8 @@ + } + }; + ++extern "C" + int stogo_minimize(int n, + objective_func fgrad, void *data, + double *x, double *minf, + const double *l, const double *u, diff --git a/tools/workspace/nlopt_internal/repository.bzl b/tools/workspace/nlopt_internal/repository.bzl new file mode 100644 index 000000000000..d2ceb9b92d84 --- /dev/null +++ b/tools/workspace/nlopt_internal/repository.bzl @@ -0,0 +1,19 @@ +# -*- python -*- + +load("@drake//tools/workspace:github.bzl", "github_archive") + +def nlopt_internal_repository( + name, + mirrors = None): + github_archive( + name = name, + repository = "stevengj/nlopt", + commit = "v2.7.1", + sha256 = "db88232fa5cef0ff6e39943fc63ab6074208831dc0031cf1545f6ecd31ae2a1a", # noqa + build_file = "@drake//tools/workspace/nlopt_internal:package.BUILD.bazel", # noqa + patches = [ + "@drake//tools/workspace/nlopt_internal:patches/remove_luksan.patch", # noqa + "@drake//tools/workspace/nlopt_internal:patches/vendoring.patch", + ], + mirrors = mirrors, + ) diff --git a/tools/workspace/nlopt_internal/test/enum_test.py b/tools/workspace/nlopt_internal/test/enum_test.py new file mode 100644 index 000000000000..d4e01a5d57f8 --- /dev/null +++ b/tools/workspace/nlopt_internal/test/enum_test.py @@ -0,0 +1,42 @@ +import re +import unittest + +from bazel_tools.tools.python.runfiles import runfiles + + +class TestEnum(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_enum_cross_check(self): + """Checks that the Drake-created flavor of nlopt.cpp (via a patch file) + is consistent with the upstream-geneated flavor of same (via CMake). + + If this test fails during an NLopt version pin upgrade, you will need + to update patches/gen_enums.patch with the reported differences. + """ + # Load both input files. + # "actual" refers to the the Drake-created flavor (via a patch file). + # "expected" refers to the upstream-geneated flavor (via CMake). + manifest = runfiles.Create() + actual_file = manifest.Rlocation( + "nlopt_internal/genrule/nlopt.hpp") + with open(actual_file) as f: + actual = f.read() + expected_file = manifest.Rlocation( + "drake/tools/workspace/nlopt_internal/test/nlopt-upstream.hpp") + with open(expected_file) as f: + expected = f.read() + + # When CMake is processing the header file, it removes blank lines. + # We will do the same to our actual file to prep for comparison. + actual = actual.replace("\n\n", "\n") + + # CMake also does something inexplicable to tab-spaced macro line + # endings. Canonicalize those in both files for comparison. + actual = re.sub(r'\s+\\', r' \\', actual) + expected = re.sub(r'\s+\\', r' \\', expected) + + # Compare + self.assertMultiLineEqual(expected, actual) diff --git a/tools/workspace/vendor_cxx.py b/tools/workspace/vendor_cxx.py index 1e8c590f3bc6..16ca1881e9b3 100644 --- a/tools/workspace/vendor_cxx.py +++ b/tools/workspace/vendor_cxx.py @@ -30,6 +30,10 @@ def _rewrite_one_text(*, text, edit_include): for old_inc, new_inc in edit_include: text = text.replace(f'#include "{old_inc}', f'#include "{new_inc}') + # If the file is a mixed C/C++ header, then we need to leave it alone. + if '\nextern "C" {\n' in text: + return text + # Add an inline namespace around the whole file, but disable it around # include statements. open_inline = ' '.join([ diff --git a/tools/workspace/vendor_cxx_test.py b/tools/workspace/vendor_cxx_test.py index a534f1370aa5..cafea53058e5 100644 --- a/tools/workspace/vendor_cxx_test.py +++ b/tools/workspace/vendor_cxx_test.py @@ -48,6 +48,24 @@ def test_simple(self): self._close, ]) + def test_extern_c(self): + self._check([ + '#include "somelib/somefile.h"', + '#include "unrelated/thing.h"', + 'extern "C" {', + 'int foo();', + '} // extern C', + ], [ + # The include paths are still changed. + '#include "drake_vendor/somelib/somefile.h"', + '#include "unrelated/thing.h"', + + # No namespaces are added. + 'extern "C" {', + 'int foo();', + '} // extern C', + ]) + assert __name__ == '__main__' unittest.main()