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()