diff --git a/setup/ubuntu/binary_distribution/packages-focal.txt b/setup/ubuntu/binary_distribution/packages-focal.txt index bc7a0f999baf..7b97b927f824 100644 --- a/setup/ubuntu/binary_distribution/packages-focal.txt +++ b/setup/ubuntu/binary_distribution/packages-focal.txt @@ -1,6 +1,5 @@ coinor-libclp1 coinor-libcoinutils3v5 -coinor-libipopt1v5 default-jre jupyter-notebook libblas3 diff --git a/setup/ubuntu/binary_distribution/packages-jammy.txt b/setup/ubuntu/binary_distribution/packages-jammy.txt index bedd3f624b61..50dcf0ab505b 100644 --- a/setup/ubuntu/binary_distribution/packages-jammy.txt +++ b/setup/ubuntu/binary_distribution/packages-jammy.txt @@ -1,6 +1,5 @@ coinor-libclp1 coinor-libcoinutils3v5 -coinor-libipopt1v5 default-jre jupyter-notebook libblas-dev diff --git a/setup/ubuntu/source_distribution/packages-focal.txt b/setup/ubuntu/source_distribution/packages-focal.txt index af0fd3cb2606..b27c7556ac4d 100644 --- a/setup/ubuntu/source_distribution/packages-focal.txt +++ b/setup/ubuntu/source_distribution/packages-focal.txt @@ -1,7 +1,6 @@ clang-format-12 coinor-libclp-dev coinor-libcoinutils-dev -coinor-libipopt-dev default-jdk file gfortran diff --git a/setup/ubuntu/source_distribution/packages-jammy.txt b/setup/ubuntu/source_distribution/packages-jammy.txt index 6e16b6925f9c..c97034cfb770 100644 --- a/setup/ubuntu/source_distribution/packages-jammy.txt +++ b/setup/ubuntu/source_distribution/packages-jammy.txt @@ -1,7 +1,6 @@ clang-format-12 coinor-libclp-dev coinor-libcoinutils-dev -coinor-libipopt-dev default-jdk file gfortran diff --git a/tools/install/libdrake/BUILD.bazel b/tools/install/libdrake/BUILD.bazel index 57130ed3f24c..13091d7deb3a 100644 --- a/tools/install/libdrake/BUILD.bazel +++ b/tools/install/libdrake/BUILD.bazel @@ -1,5 +1,6 @@ package(default_visibility = ["//visibility:private"]) +load("@bazel_skylib//lib:selects.bzl", "selects") load( "@python//:version.bzl", "PYTHON_SITE_PACKAGES_RELPATH", @@ -137,14 +138,23 @@ cc_library( }), ) -# Depend on IPOPT's shared library iff IPOPT is enabled. +# Depend on IPOPT's shared library iff IPOPT is enabled and we're on a platform +# that uses the host OS shared library. +selects.config_setting_group( + name = "ipopt_not_shared", + match_any = [ + "//tools:no_ipopt", + "//tools/cc_toolchain:linux", + ], +) + cc_library( name = "ipopt_deps", - deps = select({ + deps = selects.with_or({ "//conditions:default": [ "@ipopt", ], - "//tools:no_ipopt": [], + ":ipopt_not_shared": [], }), ) diff --git a/tools/wheel/BUILD.bazel b/tools/wheel/BUILD.bazel index d97538a11e40..5fd3e9093ac4 100644 --- a/tools/wheel/BUILD.bazel +++ b/tools/wheel/BUILD.bazel @@ -1,6 +1,11 @@ load("@drake//tools/skylark:drake_py.bzl", "drake_py_binary") load("//tools/lint:lint.bzl", "add_lint_tests") +exports_files( + glob(["**"]), + visibility = ["//tools:__subpackages__"], +) + py_library( name = "module_py", srcs = ["__init__.py"], diff --git a/tools/wheel/image/dependencies/projects.cmake b/tools/wheel/image/dependencies/projects.cmake index 7709663b80f1..4797edbbf181 100644 --- a/tools/wheel/image/dependencies/projects.cmake +++ b/tools/wheel/image/dependencies/projects.cmake @@ -90,20 +90,22 @@ set(clp_md5 "f7c25af22d2f03398cbbdf38c8b4f6fd") set(clp_dlname "clp-${clp_version}.tar.gz") list(APPEND ALL_PROJECTS clp) +# ipopt (requires mumps) if(APPLE) set(mumps_version 5.4.1) # Latest available in Ubuntu. set(mumps_url - "http://archive.ubuntu.com/ubuntu/pool/universe/m/mumps/mumps_${mumps_version}.orig.tar.gz" + "http://archive.ubuntu.com/ubuntu/pool/universe/m/mumps/mumps_${mumps_version}.orig.tar.gz" "http://mumps.enseeiht.fr/MUMPS_${mumps_version}.tar.gz" ) set(mumps_md5 "93be789bf9c6c341a78c16038da3241b") set(mumps_dlname "mumps-${mumps_version}.tar.gz") list(APPEND ALL_PROJECTS mumps) -endif() -# ipopt -set(ipopt_version 3.11.9) -set(ipopt_url "https://github.com/coin-or/Ipopt/archive/refs/tags/releases/${ipopt_version}.tar.gz") -set(ipopt_md5 "55275c202072ad30db25d2b723ef9b7a") -set(ipopt_dlname "ipopt-${ipopt_version}.tar.gz") -list(APPEND ALL_PROJECTS ipopt) + # This must match the version in tools/workspace/ipopt_internal_fromsource. + # The matching is automatically enforced by a linter script. + set(ipopt_version 3.14.12) + set(ipopt_url "https://github.com/coin-or/Ipopt/archive/refs/tags/releases/${ipopt_version}.tar.gz") + set(ipopt_md5 "b2bcb362be4c10eccde02829d3025faa") + set(ipopt_dlname "ipopt-${ipopt_version}.tar.gz") + list(APPEND ALL_PROJECTS ipopt) +endif() diff --git a/tools/wheel/image/dependencies/projects/ipopt.cmake b/tools/wheel/image/dependencies/projects/ipopt.cmake index 154ea80426e7..b530f083a0b9 100644 --- a/tools/wheel/image/dependencies/projects/ipopt.cmake +++ b/tools/wheel/image/dependencies/projects/ipopt.cmake @@ -1,24 +1,12 @@ -if(APPLE) - set(ipopt_extra_dependencies mumps) - set(ipopt_mumps_configure_args - --with-mumps-lib=-ldmumps\ -lmumps_common\ -lpord\ -lmpiseq - --with-mumps-incdir=${CMAKE_INSTALL_PREFIX}/include - CPPFLAGS=-I${CMAKE_INSTALL_PREFIX}/include/mumps_seq - ) -else() - set(ipopt_extra_dependencies) - set(ipopt_mumps_configure_args - --with-mumps-lib=-ldmumps_seq - --with-mumps-incdir=/usr/include - CPPFLAGS=-I/usr/include/mumps_seq - ) +if(NOT APPLE) + message(FATAL_ERROR "mumps should only be built on macOS") endif() ExternalProject_Add(ipopt URL ${ipopt_url} URL_MD5 ${ipopt_md5} DOWNLOAD_NAME ${ipopt_dlname} - DEPENDS lapack ${ipopt_extra_dependencies} + DEPENDS lapack mumps ${COMMON_EP_ARGS} BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./configure @@ -28,11 +16,13 @@ ExternalProject_Add(ipopt CFLAGS=-fPIC CXXFLAGS=-fPIC LDFLAGS=-L${CMAKE_INSTALL_PREFIX}/lib - ${ipopt_mumps_configure_args} + --with-mumps-lflags=-ldmumps\ -lmpiseq\ -lmumps_common\ -lpord + --with-mumps-cflags=-I${CMAKE_INSTALL_PREFIX}/include + CPPFLAGS=-I${CMAKE_INSTALL_PREFIX}/include/mumps_seq BUILD_COMMAND make INSTALL_COMMAND make install ) extract_license(ipopt - Ipopt/LICENSE + LICENSE ) diff --git a/tools/workspace/BUILD.bazel b/tools/workspace/BUILD.bazel index fb4f4262746c..bd62ab734c8d 100644 --- a/tools/workspace/BUILD.bazel +++ b/tools/workspace/BUILD.bazel @@ -50,6 +50,7 @@ drake_py_binary( "@fcl_internal//:__pkg__", "@gz_math_internal//:__pkg__", "@gz_utils_internal//:__pkg__", + "@ipopt_internal_fromsource//:__pkg__", "@msgpack_internal//:__pkg__", "@nlopt_internal//:__pkg__", "@qhull_internal//:__pkg__", @@ -85,6 +86,7 @@ _DRAKE_EXTERNAL_PACKAGE_INSTALLS = ["@%s//:install" % p for p in [ "fmt", "gz_math_internal", "gz_utils_internal", + "ipopt", "lcm", "meshcat", "msgpack_internal", diff --git a/tools/workspace/default.bzl b/tools/workspace/default.bzl index 1892a3179cc9..9d87113a0e81 100644 --- a/tools/workspace/default.bzl +++ b/tools/workspace/default.bzl @@ -37,6 +37,8 @@ load("@drake//tools/workspace/gz_math_internal:repository.bzl", "gz_math_interna load("@drake//tools/workspace/gz_utils_internal:repository.bzl", "gz_utils_internal_repository") # noqa load("@drake//tools/workspace/intel_realsense_ros_internal:repository.bzl", "intel_realsense_ros_internal_repository") # noqa load("@drake//tools/workspace/ipopt:repository.bzl", "ipopt_repository") +load("@drake//tools/workspace/ipopt_internal_fromsource:repository.bzl", "ipopt_internal_fromsource_repository") # noqa +load("@drake//tools/workspace/ipopt_internal_pkgconfig:repository.bzl", "ipopt_internal_pkgconfig_repository") # noqa load("@drake//tools/workspace/lapack:repository.bzl", "lapack_repository") load("@drake//tools/workspace/lcm:repository.bzl", "lcm_repository") load("@drake//tools/workspace/libblas:repository.bzl", "libblas_repository") @@ -51,6 +53,7 @@ load("@drake//tools/workspace/libtiff:repository.bzl", "libtiff_repository") load("@drake//tools/workspace/meshcat:repository.bzl", "meshcat_repository") load("@drake//tools/workspace/mosek:repository.bzl", "mosek_repository") load("@drake//tools/workspace/msgpack_internal:repository.bzl", "msgpack_internal_repository") # noqa +load("@drake//tools/workspace/mumps_internal:repository.bzl", "mumps_internal_repository") # noqa load("@drake//tools/workspace/mypy_extensions_internal:repository.bzl", "mypy_extensions_internal_repository") # noqa load("@drake//tools/workspace/mypy_internal:repository.bzl", "mypy_internal_repository") # noqa load("@drake//tools/workspace/nanoflann_internal:repository.bzl", "nanoflann_internal_repository") # noqa @@ -182,6 +185,10 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS): intel_realsense_ros_internal_repository(name = "intel_realsense_ros_internal", mirrors = mirrors) # noqa if "ipopt" not in excludes: ipopt_repository(name = "ipopt") + if "ipopt_internal_fromsource" not in excludes: + ipopt_internal_fromsource_repository(name = "ipopt_internal_fromsource", mirrors = mirrors) # noqa + if "ipopt_internal_pkgconfig" not in excludes: + ipopt_internal_pkgconfig_repository(name = "ipopt_internal_pkgconfig") if "lapack" not in excludes: lapack_repository(name = "lapack") if "lcm" not in excludes: @@ -210,6 +217,8 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS): mosek_repository(name = "mosek") if "msgpack_internal" not in excludes: msgpack_internal_repository(name = "msgpack_internal", mirrors = mirrors) # noqa + if "mumps_internal" not in excludes: + mumps_internal_repository(name = "mumps_internal") if "mypy_extensions_internal" not in excludes: mypy_extensions_internal_repository(name = "mypy_extensions_internal", mirrors = mirrors) # noqa if "mypy_internal" not in excludes: diff --git a/tools/workspace/ipopt/BUILD.bazel b/tools/workspace/ipopt/BUILD.bazel index b77b93ae0dbc..67914ea7e0a0 100644 --- a/tools/workspace/ipopt/BUILD.bazel +++ b/tools/workspace/ipopt/BUILD.bazel @@ -1,6 +1,3 @@ -# This file exists to make our directory into a Bazel package, so that our -# neighboring *.bzl file can be loaded elsewhere. - load("//tools/lint:lint.bzl", "add_lint_tests") add_lint_tests() diff --git a/tools/workspace/ipopt/repository.bzl b/tools/workspace/ipopt/repository.bzl index e0fc5a311a7f..8e5671caa829 100644 --- a/tools/workspace/ipopt/repository.bzl +++ b/tools/workspace/ipopt/repository.bzl @@ -1,22 +1,17 @@ -load( - "@drake//tools/workspace:pkg_config.bzl", - "pkg_config_repository", -) +load("@drake//tools/workspace:os.bzl", "os_specific_alias_repository") -def ipopt_repository( - name, - licenses = [ - "reciprocal", # CPL-1.0 - "unencumbered", # Public-Domain - ], - modname = "ipopt", - pkg_config_paths = [], - homebrew_subdir = "opt/ipopt/lib/pkgconfig", - **kwargs): - pkg_config_repository( +# How we build IPOPT depends on which platform we're on. +def ipopt_repository(name): + os_specific_alias_repository( name = name, - licenses = licenses, - modname = modname, - pkg_config_paths = pkg_config_paths, - **kwargs + mapping = { + "macOS default": [ + "ipopt=@ipopt_internal_pkgconfig//:ipopt_internal_pkgconfig", + "install=@ipopt_internal_pkgconfig//:install", + ], + "Ubuntu default": [ + "ipopt=@ipopt_internal_fromsource//:ipopt", + "install=@ipopt_internal_fromsource//:install", + ], + }, ) diff --git a/tools/workspace/ipopt_internal_fromsource/BUILD.bazel b/tools/workspace/ipopt_internal_fromsource/BUILD.bazel new file mode 100644 index 000000000000..0ee18c093828 --- /dev/null +++ b/tools/workspace/ipopt_internal_fromsource/BUILD.bazel @@ -0,0 +1,15 @@ +load("@drake//tools/skylark:drake_py.bzl", "drake_py_unittest") +load("//tools/lint:lint.bzl", "add_lint_tests") + +drake_py_unittest( + name = "lint_test", + data = [ + ":package.BUILD.bazel", + "//tools/wheel:image/dependencies/projects.cmake", + "@ipopt_internal_fromsource//:drake_repository_metadata.json", + "@ipopt_internal_fromsource//:src/Makefile.am", + ], + tags = ["lint"], +) + +add_lint_tests() diff --git a/tools/workspace/ipopt_internal_fromsource/package.BUILD.bazel b/tools/workspace/ipopt_internal_fromsource/package.BUILD.bazel new file mode 100644 index 000000000000..bb20ee18c84e --- /dev/null +++ b/tools/workspace/ipopt_internal_fromsource/package.BUILD.bazel @@ -0,0 +1,512 @@ +# -*- bazel -*- + +load("@bazel_skylib//lib:paths.bzl", "paths") +load( + "@drake//tools/install:install.bzl", + "install", +) +load( + "@drake//tools/skylark:drake_cc.bzl", + "cc_linkonly_library", +) +load( + "@drake//tools/workspace:cmake_configure_file.bzl", + "cmake_configure_file", +) +load( + "@drake//tools/workspace:vendor_cxx.bzl", + "cc_library_vendored", +) + +licenses(["reciprocal"]) # EPL-2.0 + +package(default_visibility = ["//visibility:private"]) + +exports_files([ + # Always provide access to license texts. + "LICENSE", + # This is used by our lint_test. + "src/Makefile.am", +]) + +# The list of headers to expose to the user (i.e., Drake). +# +# This is exactly the includeipopt_HEADERS from upstream, as enforced by +# drake/tools/workspace/ipopt_internal/test/lint_test.py. +_HDRS_PUBLIC = [ + "src/Common/IpCachedResults.hpp", + "src/Common/IpDebug.hpp", + "src/Common/IpException.hpp", + "src/Common/IpJournalist.hpp", + "src/Common/IpLibraryLoader.hpp", + "src/Common/IpObserver.hpp", + "src/Common/IpOptionsList.hpp", + "src/Common/IpReferenced.hpp", + "src/Common/IpRegOptions.hpp", + "src/Common/IpSmartPtr.hpp", + "src/Common/IpTaggedObject.hpp", + "src/Common/IpTimedTask.hpp", + "src/Common/IpTypes.hpp", + "src/Common/IpTypes.h", + "src/Common/IpUtils.hpp", + "src/LinAlg/IpBlas.hpp", + "src/LinAlg/IpCompoundMatrix.hpp", + "src/LinAlg/IpCompoundSymMatrix.hpp", + "src/LinAlg/IpCompoundVector.hpp", + "src/LinAlg/IpDenseVector.hpp", + "src/LinAlg/IpDiagMatrix.hpp", + "src/LinAlg/IpExpansionMatrix.hpp", + "src/LinAlg/IpIdentityMatrix.hpp", + "src/LinAlg/IpLapack.hpp", + "src/LinAlg/IpMatrix.hpp", + "src/LinAlg/IpScaledMatrix.hpp", + "src/LinAlg/IpSumSymMatrix.hpp", + "src/LinAlg/IpSymMatrix.hpp", + "src/LinAlg/IpSymScaledMatrix.hpp", + "src/LinAlg/IpVector.hpp", + "src/LinAlg/IpZeroSymMatrix.hpp", + "src/LinAlg/TMatrices/IpGenTMatrix.hpp", + "src/LinAlg/TMatrices/IpSymTMatrix.hpp", + "src/LinAlg/TMatrices/IpTripletHelper.hpp", + "src/Algorithm/IpAlgBuilder.hpp", + "src/Algorithm/IpAlgStrategy.hpp", + "src/Algorithm/IpAugSystemSolver.hpp", + "src/Algorithm/IpConvCheck.hpp", + "src/Algorithm/IpEqMultCalculator.hpp", + "src/Algorithm/IpHessianUpdater.hpp", + "src/Algorithm/IpIpoptAlg.hpp", + "src/Algorithm/IpIpoptCalculatedQuantities.hpp", + "src/Algorithm/IpIpoptData.hpp", + "src/Algorithm/IpIpoptNLP.hpp", + "src/Algorithm/IpIterateInitializer.hpp", + "src/Algorithm/IpIteratesVector.hpp", + "src/Algorithm/IpIterationOutput.hpp", + "src/Algorithm/IpOrigIpoptNLP.hpp", + "src/Algorithm/IpLineSearch.hpp", + "src/Algorithm/IpMuUpdate.hpp", + "src/Algorithm/IpNLPScaling.hpp", + "src/Algorithm/IpPDSystemSolver.hpp", + "src/Algorithm/IpSearchDirCalculator.hpp", + "src/Algorithm/IpStdAugSystemSolver.hpp", + "src/Algorithm/IpTimingStatistics.hpp", + "src/Algorithm/LinearSolvers/IpLinearSolvers.h", + "src/Algorithm/LinearSolvers/IpSlackBasedTSymScalingMethod.hpp", + "src/Algorithm/LinearSolvers/IpSparseSymLinearSolverInterface.hpp", + "src/Algorithm/LinearSolvers/IpSymLinearSolver.hpp", + "src/Algorithm/LinearSolvers/IpTripletToCSRConverter.hpp", + "src/Algorithm/LinearSolvers/IpTSymLinearSolver.hpp", + "src/Algorithm/LinearSolvers/IpTSymScalingMethod.hpp", + "src/Interfaces/IpAlgTypes.hpp", + "src/Interfaces/IpIpoptApplication.hpp", + "src/Interfaces/IpNLP.hpp", + "src/Interfaces/IpReturnCodes.h", + "src/Interfaces/IpReturnCodes.hpp", + "src/Interfaces/IpReturnCodes_inc.h", + "src/Interfaces/IpReturnCodes.inc", + "src/Interfaces/IpSolveStatistics.hpp", + "src/Interfaces/IpStdCInterface.h", + "src/Interfaces/IpTNLP.hpp", + "src/Interfaces/IpTNLPAdapter.hpp", + "src/Interfaces/IpTNLPReducer.hpp", +] + +# The include paths for _HDRS_PUBLIC. +_INCLUDES_PUBLIC = depset([paths.dirname(x) for x in _HDRS_PUBLIC]).to_list() + +# The baseline list of sources to compile. +# +# This is exactly the initial libipopt_la_SOURCES from upstream but without the +# problematic IpStdCInterface.cpp and IpStdFInterface.c files which we don't +# use and cannot easily be marked as hidden. The correct value for this list is +# enforced by drake/tools/workspace/ipopt_internal/test/lint_test.py. +# +# The Makefile.am conditionally adds more sources depending on other +# configuration options. Likewise, we'll add some more sources later on. +_SRCS_INITIAL = [ + "src/Common/IpDebug.cpp", + "src/Common/IpJournalist.cpp", + "src/Common/IpObserver.cpp", + "src/Common/IpOptionsList.cpp", + "src/Common/IpRegOptions.cpp", + "src/Common/IpTaggedObject.cpp", + "src/Common/IpUtils.cpp", + "src/Common/IpLibraryLoader.cpp", + "src/LinAlg/IpBlas.cpp", + "src/LinAlg/IpCompoundMatrix.cpp", + "src/LinAlg/IpCompoundSymMatrix.cpp", + "src/LinAlg/IpCompoundVector.cpp", + "src/LinAlg/IpDenseGenMatrix.cpp", + "src/LinAlg/IpDenseSymMatrix.cpp", + "src/LinAlg/IpDenseVector.cpp", + "src/LinAlg/IpDiagMatrix.cpp", + "src/LinAlg/IpExpandedMultiVectorMatrix.cpp", + "src/LinAlg/IpExpansionMatrix.cpp", + "src/LinAlg/IpIdentityMatrix.cpp", + "src/LinAlg/IpLapack.cpp", + "src/LinAlg/IpLowRankUpdateSymMatrix.cpp", + "src/LinAlg/IpMatrix.cpp", + "src/LinAlg/IpMultiVectorMatrix.cpp", + "src/LinAlg/IpScaledMatrix.cpp", + "src/LinAlg/IpSumMatrix.cpp", + "src/LinAlg/IpSumSymMatrix.cpp", + "src/LinAlg/IpSymScaledMatrix.cpp", + "src/LinAlg/IpTransposeMatrix.cpp", + "src/LinAlg/IpVector.cpp", + "src/LinAlg/IpZeroMatrix.cpp", + "src/LinAlg/IpZeroSymMatrix.cpp", + "src/LinAlg/TMatrices/IpGenTMatrix.cpp", + "src/LinAlg/TMatrices/IpSymTMatrix.cpp", + "src/LinAlg/TMatrices/IpTripletHelper.cpp", + "src/Algorithm/IpAdaptiveMuUpdate.cpp", + "src/Algorithm/IpAlgBuilder.cpp", + "src/Algorithm/IpAlgorithmRegOp.cpp", + "src/Algorithm/IpAugRestoSystemSolver.cpp", + "src/Algorithm/IpBacktrackingLineSearch.cpp", + "src/Algorithm/IpDefaultIterateInitializer.cpp", + "src/Algorithm/IpEquilibrationScaling.cpp", + "src/Algorithm/IpExactHessianUpdater.cpp", + "src/Algorithm/IpFilter.cpp", + "src/Algorithm/IpFilterLSAcceptor.cpp", + "src/Algorithm/IpGenAugSystemSolver.cpp", + "src/Algorithm/IpGradientScaling.cpp", + "src/Algorithm/IpIpoptAlg.cpp", + "src/Algorithm/IpIpoptCalculatedQuantities.cpp", + "src/Algorithm/IpIpoptData.cpp", + "src/Algorithm/IpIteratesVector.cpp", + "src/Algorithm/IpLeastSquareMults.cpp", + "src/Algorithm/IpLimMemQuasiNewtonUpdater.cpp", + "src/Algorithm/IpLoqoMuOracle.cpp", + "src/Algorithm/IpLowRankAugSystemSolver.cpp", + "src/Algorithm/IpLowRankSSAugSystemSolver.cpp", + "src/Algorithm/IpMonotoneMuUpdate.cpp", + "src/Algorithm/IpNLPBoundsRemover.cpp", + "src/Algorithm/IpNLPScaling.cpp", + "src/Algorithm/IpOptErrorConvCheck.cpp", + "src/Algorithm/IpOrigIpoptNLP.cpp", + "src/Algorithm/IpOrigIterationOutput.cpp", + "src/Algorithm/IpPDFullSpaceSolver.cpp", + "src/Algorithm/IpPDPerturbationHandler.cpp", + "src/Algorithm/IpPDSearchDirCalc.cpp", + "src/Algorithm/IpPenaltyLSAcceptor.cpp", + "src/Algorithm/IpProbingMuOracle.cpp", + "src/Algorithm/IpQualityFunctionMuOracle.cpp", + "src/Algorithm/IpRestoConvCheck.cpp", + "src/Algorithm/IpRestoFilterConvCheck.cpp", + "src/Algorithm/IpRestoIpoptNLP.cpp", + "src/Algorithm/IpRestoIterateInitializer.cpp", + "src/Algorithm/IpRestoIterationOutput.cpp", + "src/Algorithm/IpRestoMinC_1Nrm.cpp", + "src/Algorithm/IpRestoPenaltyConvCheck.cpp", + "src/Algorithm/IpRestoRestoPhase.cpp", + "src/Algorithm/IpStdAugSystemSolver.cpp", + "src/Algorithm/IpTimingStatistics.cpp", + "src/Algorithm/IpUserScaling.cpp", + "src/Algorithm/IpWarmStartIterateInitializer.cpp", + "src/Algorithm/LinearSolvers/IpLinearSolversRegOp.cpp", + "src/Algorithm/LinearSolvers/IpLinearSolvers.c", + "src/Algorithm/LinearSolvers/IpSlackBasedTSymScalingMethod.cpp", + "src/Algorithm/LinearSolvers/IpTripletToCSRConverter.cpp", + "src/Algorithm/LinearSolvers/IpTSymDependencyDetector.cpp", + "src/Algorithm/LinearSolvers/IpTSymLinearSolver.cpp", + "src/contrib/CGPenalty/IpCGPenaltyCq.cpp", + "src/contrib/CGPenalty/IpCGPenaltyData.cpp", + "src/contrib/CGPenalty/IpCGPenaltyLSAcceptor.cpp", + "src/contrib/CGPenalty/IpCGPenaltyRegOp.cpp", + "src/contrib/CGPenalty/IpCGPerturbationHandler.cpp", + "src/contrib/CGPenalty/IpCGSearchDirCalc.cpp", + "src/contrib/CGPenalty/IpPiecewisePenalty.cpp", + "src/Interfaces/IpInterfacesRegOp.cpp", + "src/Interfaces/IpIpoptApplication.cpp", + "src/Interfaces/IpSolveStatistics.cpp", + "src/Interfaces/IpStdInterfaceTNLP.cpp", + "src/Interfaces/IpTNLP.cpp", + "src/Interfaces/IpTNLPAdapter.cpp", + "src/Interfaces/IpTNLPReducer.cpp", +] + +# In addition to _SRCS_INITIAL, we also add extra sources for certain solvers. +# +# Mumps is our preferred linear solver. +_SRCS_SOLVER_MUMPS = [ + "src/Algorithm/LinearSolvers/IpMumpsSolverInterface.cpp", +] + +# In addition to _SRCS_INITIAL, we also add extra sources for certain solvers. +# These (unused) solvers are still required at link-time. +# +# This list is cross-checked vs the Makefile.am contents via +# drake/tools/workspace/ipopt_internal/test/lint_test.py. +_SRCS_SOLVER_INT32 = [ + "src/Algorithm/LinearSolvers/IpMc19TSymScalingMethod.cpp", + "src/Algorithm/LinearSolvers/IpMa27TSolverInterface.cpp", + "src/Algorithm/LinearSolvers/IpMa57TSolverInterface.cpp", + "src/Algorithm/LinearSolvers/IpMa77SolverInterface.cpp", + "src/Algorithm/LinearSolvers/IpMa86SolverInterface.cpp", + "src/Algorithm/LinearSolvers/IpMa97SolverInterface.cpp", + "src/Algorithm/LinearSolvers/IpPardisoSolverInterface.cpp", +] + +# All of the sources together (baseline + extra). +_SRCS = _SRCS_INITIAL + _SRCS_SOLVER_MUMPS + _SRCS_SOLVER_INT32 + +# When compiling the sources, we'll provide access to _HDRS_PRIVATE, which is +# all of the headers that live in the same directories as the sources. These +# are the include paths for _HDRS_PRIVATE. +_INCLUDES_PRIVATE = depset([paths.dirname(x) for x in _SRCS]).to_list() + +# All of the headers that live in the same directories as the sources (except +# for the config headers which we need to handle separately, below). +_HDRS_PRIVATE = glob([ + x + "/*.h*" + for x in _INCLUDES_PRIVATE +], exclude = [ + "src/Common/config*", + "src/Common/IpoptConfig.h", +], allow_empty = False) + +# The next three rules are for the private flavor of IpoptConfig.h. +# +# The upstream IpoptConfig.h is a tricksy little beast. When compiling the +# library source code, it refers to the configure-generated header. When +# installing into include paths for the user, it uses something different +# (a narrower header with just the IPOPT version numbers & etc). +# +# Here we'll generate the private header with the configuration we want, +# by respelling it from autoconf to cmake and then setting our definitions. +# +# We can use cc_library (not cc_library_vendored) to declare the header +# because it only has preprocessor definitions (no C++ object code). +genrule( + name = "_respell_autoconf_to_cmakeconfig", + srcs = ["src/Common/config.h.in"], + outs = ["src/Common/config.h.in.cmake"], + cmd = "sed -e 's|#undef \\(.*\\)|#cmakedefine \\1 @\\1@|' $< > $@", +) + +cmake_configure_file( + name = "_configure", + src = ":src/Common/config.h.in.cmake", + out = "hdr_private/IpoptConfig.h", + defines = [ + "IPOPT_VERSION=\"drake_vendor\"", + # Features of the standard library and/or host system. + "HAVE_CFLOAT=1", + "HAVE_CIEEEFP=1", + "HAVE_CMATH=1", + "HAVE_FLOAT_H=1", + "HAVE_IEEEFP_H=1", + "HAVE_INTTYPES_H=1", + "HAVE_MATH_H=1", + "HAVE_STDINT_H=1", + "HAVE_STDIO_H=1", + "HAVE_STDLIB_H=1", + "HAVE_STRINGS_H=1", + "HAVE_STRING_H=1", + "HAVE_SYS_STAT_H=1", + "HAVE_SYS_TYPES_H=1", + "HAVE_UNISTD_H=1", + "HAVE_VSNPRINTF=1", + "IPOPT_C_FINITE=std::isfinite", + "IPOPT_HAS_RAND=1", + "IPOPT_HAS_STD__RAND=1", + "IPOPT_HAS_VA_COPY=1", + "STDC_HEADERS=1", + # Optional dependencies that we do actually want to use. + "IPOPT_HAS_LAPACK=1", + "IPOPT_HAS_MUMPS=1", + # No debug self-checks (the default). + "IPOPT_CHECKLEVEL=0", + "IPOPT_VERBOSITY=0", + # These are no-ops, but they can't be omitted. + "HSLLIB_EXPORT=", + "IPOPTAMPLINTERFACELIB_EXPORT=", + "IPOPTLIB_EXPORT=__attribute__ ((visibility (\"hidden\")))", + "SIPOPTAMPLINTERFACELIB_EXPORT=", + "SIPOPTLIB_EXPORT=", + ], + undefines = [ + # Don't use these features of the standard library and/or host system. + "CXX_NO_MINUS_C_MINUS_O", + "F77_DUMMY_MAIN", + "F77_FUNC", + "F77_FUNC_", + "F77_NO_MINUS_C_MINUS_O", + "FC_DUMMY_MAIN_EQ_F77", + "HAVE_DLFCN_H", + "HAVE__VSNPRINTF", + "IPOPT_HAS_DRAND48", + "IPOPT_HAS_FEENABLEEXCEPT", + "IPOPT_INT64", + "IPOPT_SINGLE", + # Optional dependencies that we don't use. + "BUILD_INEXACT", + "HAVE_MPI_INITIALIZED", + "HAVE_WINDOWS_H", + "IPOPT_HAS_ASL", + "IPOPT_HAS_HSL", + "IPOPT_HAS_LINEARSOLVERLOADER", + "IPOPT_HAS_PARDISO_MKL", + "IPOPT_HAS_SPRAL", + "IPOPT_HAS_WSMP", + "IPOPT_HSL_FUNC", + "IPOPT_HSL_FUNC_", + "IPOPT_MPIINIT", + "IPOPT_WSMP_FUNC", + "IPOPT_WSMP_FUNC_", + "PARDISO_LIB", + # Chaff that's not used by the C++ code anyway. + "IPOPT_LAPACK_FUNC_", + "IPOPT_VERSION_MAJOR", + "IPOPT_VERSION_MINOR", + "IPOPT_VERSION_RELEASE", + "LT_OBJDIR", + "PACKAGE_BUGREPORT", + "PACKAGE_NAME", + "PACKAGE_STRING", + "PACKAGE_TARNAME", + "PACKAGE_URL", + "PACKAGE_VERSION", + "SIZEOF_INT_P", + # This is actually used by the C++ code, but cmake_configure_file can't + # handle it. We'll use `defines = []` for this instead (see below). + "IPOPT_LAPACK_FUNC", + ], + strict = True, +) + +cc_library( + name = "_ipopt_config_private", + hdrs = [":hdr_private/IpoptConfig.h"], + strip_include_prefix = "hdr_private", + defines = [ + "IPOPT_LAPACK_FUNC(name,NAME)=name##_", + ], + linkstatic = True, +) + +# Compile all of the object code for the library. +cc_library_vendored( + name = "_build", + srcs = _SRCS, + srcs_vendored = [ + x.replace("src/", "drake_src/") + for x in _SRCS + ], + hdrs = _HDRS_PRIVATE, + hdrs_vendored = [ + x.replace("src/", "drake_src/") + for x in _HDRS_PRIVATE + ], + includes = [ + x.replace("src/", "drake_src/") + for x in _INCLUDES_PRIVATE + ], + vendor_tool_args = [ + "--no-inline-namespace", + ], + linkstatic = True, + deps = [ + ":_ipopt_config_private", + "@blas", + "@lapack", + "@mumps_internal//:dmumps_seq", + ], +) + +# Discard the headers, leaving only the object code. +cc_linkonly_library( + name = "_objs", + deps = [":_build"], +) + +# The next two rules are for the public flavor of IpoptConfig.h. +# +# We can use cc_library (not cc_library_vendored) to declare the header +# because it only has preprocessor definitions (no C++ object code). +genrule( + name = "_ipopt_config_public_genrule", + srcs = ["src/Common/config_ipopt_default.h"], + outs = ["hdr_public/IpoptConfig.h"], + cmd = "cp $< $@", +) + +cc_library( + name = "_ipopt_config_public", + hdrs = [":hdr_public/IpoptConfig.h"], + strip_include_prefix = "hdr_public", + linkstatic = True, +) + +# Assemble the public headers + object code into the library Drake will use. +cc_library_vendored( + name = "ipopt", + hdrs = _HDRS_PUBLIC, + hdrs_vendored = [ + x.replace("src/", "drake_hdr/") + for x in _HDRS_PUBLIC + ], + includes = [ + x.replace("src/", "drake_hdr/") + for x in _INCLUDES_PUBLIC + ], + vendor_tool_args = [ + "--no-inline-namespace", + ], + linkstatic = True, + deps = [ + ":_ipopt_config_public", + ":_objs", + ], + visibility = ["//visibility:public"], +) + +# The next three stanzas create a patch file of our diffs to install alongside +# our binaries (which is required by Ipopt's EPL-2.0 license). +_DIFF_INPUTS = [ + ("src/Common/config.h.in", "hdr_private/IpoptConfig.h"), +] + zip( + _SRCS + _HDRS_PRIVATE, + [ + x.replace("src/", "drake_src/") + for x in _SRCS + _HDRS_PRIVATE + ], +) + +[ + genrule( + name = "genrule_{}_patch".format(vendor_src), + srcs = [upstream_src, vendor_src], + outs = [vendor_src + ".patch"], + cmd = " ".join([ + "(diff -u0", + "--label={upstream_src} $(execpath {upstream_src})", + "--label={vendor_src} $(execpath {vendor_src})", + "> $@ || [[ $$? == 1 ]])", + ]).format( + upstream_src = upstream_src, + vendor_src = vendor_src, + ), + ) + for upstream_src, vendor_src in _DIFF_INPUTS +] + +genrule( + name = "genrule_full_patch", + srcs = [ + vendor_src + ".patch" + for (_, vendor_src) in _DIFF_INPUTS + ], + outs = ["drake_ipopt.patch"], + cmd = "cat $(SRCS) > $@", +) + +install( + name = "install", + docs = [ + "LICENSE", + ":drake_ipopt.patch", + ], + visibility = ["//visibility:public"], +) + +exports_files(["drake_repository_metadata.json"]) diff --git a/tools/workspace/ipopt_internal_fromsource/repository.bzl b/tools/workspace/ipopt_internal_fromsource/repository.bzl new file mode 100644 index 000000000000..6ac016362530 --- /dev/null +++ b/tools/workspace/ipopt_internal_fromsource/repository.bzl @@ -0,0 +1,16 @@ +load( + "@drake//tools/workspace:github.bzl", + "github_archive", +) + +def ipopt_internal_fromsource_repository( + name, + mirrors = None): + github_archive( + name = name, + repository = "coin-or/Ipopt", + commit = "releases/3.14.12", + sha256 = "6b06cd6280d5ca52fc97ca95adaaddd43529e6e8637c274e21ee1072c3b4577f", # noqa + build_file = ":package.BUILD.bazel", + mirrors = mirrors, + ) diff --git a/tools/workspace/ipopt_internal_fromsource/test/lint_test.py b/tools/workspace/ipopt_internal_fromsource/test/lint_test.py new file mode 100644 index 000000000000..f76d9e6dbd96 --- /dev/null +++ b/tools/workspace/ipopt_internal_fromsource/test/lint_test.py @@ -0,0 +1,118 @@ +import json +import unittest + + +class IpoptLintTest(unittest.TestCase): + + def _read(self, filename): + """Returns the contents of the given filename.""" + with open(filename, encoding="utf-8") as f: + return f.read() + + def _parse_build(self, varname): + """Parses a constant list of filenames from a BUILD file. + The only supported format is like this: + + {varname} = [ + "path/to/file1", + "path/to/file2", + ] + """ + result = [] + contents = self._read( + "tools/workspace/ipopt_internal_fromsource/package.BUILD.bazel") + lines = contents.splitlines() + start_line = f"{varname} = [" + end_line = "]" + start_index = lines.index(start_line) + end_index = lines.index(end_line, start_index + 1) + for i in range(start_index + 1, end_index): + line = lines[i] + prefix = ' "' + suffix = '",' + self.assertTrue(line.startswith(prefix), line) + self.assertTrue(line.endswith(suffix), line) + result.append(line[len(prefix):-len(suffix)]) + return set(result) + + def _parse_make(self, varname, *, guard_line=None): + """Parses a constant list of filenames from Makefile.am. + + The only supported formats are like this (with no `guard_line`): + + {varname} = \ + path/to/file1 \ + path/to/file2 + + Or this (with a `guard_line`): + + {guard_line} + {varname} += \ + path/to/file1 \ + path/to/file2 + """ + result = [] + contents = self._read( + "external/ipopt_internal_fromsource/src/Makefile.am") + lines = contents.splitlines() + if guard_line is None: + start_line = f"{varname} = \\" + index = lines.index(start_line) + 1 + else: + index = lines.index(guard_line) + 1 + self.assertEqual(lines[index], f"{varname} += \\") + index += 1 + while True: + line = lines[index] + has_slash = line.endswith("\\") + if has_slash: + line = line[:-1] + result.append("src/" + line.strip()) + index += 1 + if not has_slash: + break + return set(result) + + def test_hdrs_public(self): + """Checks that _HDRS_PUBLIC matches includeipopt_HEADERS.""" + self.assertSetEqual(self._parse_build("_HDRS_PUBLIC"), + self._parse_make("includeipopt_HEADERS")) + + def test_srcs_initial(self): + """Checks that _SRCS_INITIAL matches libipopt_la_SOURCES, except for + two specific unwanted sources. + """ + make_sources = self._parse_make("libipopt_la_SOURCES") + make_sources.remove("src/Interfaces/IpStdCInterface.cpp") + make_sources.remove("src/Interfaces/IpStdFInterface.c") + self.assertSetEqual(self._parse_build("_SRCS_INITIAL"), + make_sources) + + def test_srcs_solver_int32(self): + """Checks that _SRCS_SOLVER_INT32 matches !IPOPT_INT64's effect.""" + self.assertSetEqual(self._parse_build("_SRCS_SOLVER_INT32"), + self._parse_make("libipopt_la_SOURCES", + guard_line="if !IPOPT_INT64")) + + def test_wheel_verison_pin(self): + """Checks that the repository rule and wheel agree on which version of + IPOPT we should be using. + """ + # Parse the Bazel version. + commit = json.loads(self._read( + "external/ipopt_internal_fromsource/" + "drake_repository_metadata.json"))["commit"] + prefix = "releases/" + self.assertTrue(commit.startswith(prefix), commit) + bazel_version = commit[len(prefix):] + + # Parse the Wheel version from the line `set(ipopt_version #.#.#)`. + projects = self._read( + "tools/wheel/image/dependencies/projects.cmake") + prefix = "set(ipopt_version " + start = projects.index(prefix) + len(prefix) + end = projects.index(")", start) + wheel_version = projects[start:end] + + # Exact string match. + self.assertEqual(wheel_version, bazel_version) diff --git a/tools/workspace/ipopt_internal_pkgconfig/BUILD.bazel b/tools/workspace/ipopt_internal_pkgconfig/BUILD.bazel new file mode 100644 index 000000000000..67914ea7e0a0 --- /dev/null +++ b/tools/workspace/ipopt_internal_pkgconfig/BUILD.bazel @@ -0,0 +1,3 @@ +load("//tools/lint:lint.bzl", "add_lint_tests") + +add_lint_tests() diff --git a/tools/workspace/ipopt_internal_pkgconfig/repository.bzl b/tools/workspace/ipopt_internal_pkgconfig/repository.bzl new file mode 100644 index 000000000000..aa514a269d64 --- /dev/null +++ b/tools/workspace/ipopt_internal_pkgconfig/repository.bzl @@ -0,0 +1,26 @@ +load( + "@drake//tools/workspace:pkg_config.bzl", + "pkg_config_repository", +) + +def ipopt_internal_pkgconfig_repository( + name, + licenses = [ + "reciprocal", # CPL-1.0 + ], + modname = "ipopt", + pkg_config_paths = [], + homebrew_subdir = "opt/ipopt/lib/pkgconfig", + **kwargs): + pkg_config_repository( + name = name, + licenses = licenses, + modname = modname, + pkg_config_paths = pkg_config_paths, + # When using ipopt from pkg-config, there is nothing to install. + build_epilog = """ +load("@drake//tools/install:install.bzl", "install") +install(name = "install") +""", + **kwargs + ) diff --git a/tools/workspace/mumps_internal/BUILD.bazel b/tools/workspace/mumps_internal/BUILD.bazel new file mode 100644 index 000000000000..67914ea7e0a0 --- /dev/null +++ b/tools/workspace/mumps_internal/BUILD.bazel @@ -0,0 +1,3 @@ +load("//tools/lint:lint.bzl", "add_lint_tests") + +add_lint_tests() diff --git a/tools/workspace/mumps_internal/package.BUILD.bazel b/tools/workspace/mumps_internal/package.BUILD.bazel new file mode 100644 index 000000000000..8724737d2b79 --- /dev/null +++ b/tools/workspace/mumps_internal/package.BUILD.bazel @@ -0,0 +1,18 @@ +# -*- bazel -*- + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "dmumps_seq", + hdrs = glob([ + "include/**", + ], allow_empty = False), + includes = [ + "include", + "include/mumps_seq", + ], + linkopts = [ + "-ldmumps_seq", + ], + licenses = ["reciprocal"], # CECILL-C + BSD-3-Clause + Public Domain +) diff --git a/tools/workspace/mumps_internal/repository.bzl b/tools/workspace/mumps_internal/repository.bzl new file mode 100644 index 000000000000..27a4b6ee1a7b --- /dev/null +++ b/tools/workspace/mumps_internal/repository.bzl @@ -0,0 +1,27 @@ +def _impl(repo_ctx): + # Symlink the relevant headers. + hdrs = [ + "dmumps_c.h", + "mumps_c_types.h", + "mumps_compat.h", + "mumps_int_def.h", + "mumps_seq/elapse.h", + "mumps_seq/mpi.h", + "mumps_seq/mpif.h", + ] + for hdr in hdrs: + repo_ctx.symlink("/usr/include/" + hdr, "include/" + hdr) + + # Add the BUILD file. + repo_ctx.symlink( + Label("@drake//tools/workspace/mumps_internal:package.BUILD.bazel"), + "BUILD.bazel", + ) + +mumps_internal_repository = repository_rule( + doc = """Adds a repository rule for the host mumps library from Ubuntu. + This repository is not used on macOS. + """, + local = True, + implementation = _impl, +) diff --git a/tools/workspace/vendor_cxx.bzl b/tools/workspace/vendor_cxx.bzl index 49c251fbc738..1a3296836651 100644 --- a/tools/workspace/vendor_cxx.bzl +++ b/tools/workspace/vendor_cxx.bzl @@ -5,6 +5,7 @@ def cc_library_vendored( edit_include = None, srcs = None, srcs_vendored = None, + vendor_tool_args = None, **kwargs): """ Compiles a third-party C++ library using altered include paths and @@ -46,7 +47,7 @@ def cc_library_vendored( outs = hdrs_vendored + srcs_vendored, cmd = " ".join([ "$(execpath @drake//tools/workspace:vendor_cxx)", - ] + [ + ] + (vendor_tool_args or []) + [ "'--edit-include={}:{}'".format(k, v) for k, v in edit_include.items() ] + [ diff --git a/tools/workspace/vendor_cxx.py b/tools/workspace/vendor_cxx.py index 699a05928d56..991a542d6b19 100644 --- a/tools/workspace/vendor_cxx.py +++ b/tools/workspace/vendor_cxx.py @@ -131,7 +131,7 @@ class Flag(Enum): return [x == Flag.WRAP for x in flags] -def _rewrite_one_text(*, text, edit_include): +def _rewrite_one_text(*, text, edit_include, inline_namespace): """Rewrites the C++ file contents in `text` with specific alterations: - The paths in #include statements are replaced per the (old, new) pairs in @@ -141,6 +141,13 @@ def _rewrite_one_text(*, text, edit_include): - Wraps an inline namespace "drake_vendor" with hidden symbol visibility around all of the code in file (but not any #include statements). + - Or when inline_namespace is False, simply marks all of the existing + namespaces as hidden without any extra inline namespace wrapping. + This does not hide the vendored library as thoroughly (it's still + a potential ODR conflict during static linking) but has the benefit + of working on more complicated projects that our wrapping heuristics + cannot handle. + Returns the new C++ contents. These changes should suffice for the most typical flavors of C++ code. @@ -157,18 +164,34 @@ def _rewrite_one_text(*, text, edit_include): if '\nextern "C" {\n' in text: return text - # We'll add an inline namespace around the C++ code in this file. - # Designate each line of the file for whether it should be wrapped. + # Prepare to edit one line at a time. lines = text.split('\n') if lines[-1] == '': lines.pop() + hidden = '__attribute__ ((visibility ("hidden")))' + + # If we are only changing namespaces (not adding new ones), do that now: + if not inline_namespace: + # Match either 'namespace foo' or 'namespace foo {'. + regex = re.compile(r'^\s*namespace\s+([^{]+?)(\s*{)?$') + for i, line in enumerate(lines): + match = regex.match(line) + if not match: + continue + name, brace = match.groups() + lines[i] = f'namespace {name} {hidden}{brace or ""}' + text = '\n'.join(lines) + '\n' + return text + + # We'll add an inline namespace around the C++ code in this file. + # Designate each line of the file for whether it should be wrapped. should_wrap = _designate_wrapped_lines(lines) # Anytime the sense of wrapping switches, we'll insert a line. # Do this in reverse order so that the indices into lines[] are stable. open_inline = ' '.join([ 'inline namespace drake_vendor', - '__attribute__ ((visibility ("hidden")))', + hidden, '{']) close_inline = '} /* inline namespace drake_vendor */' for i in range(len(lines), -1, -1): @@ -183,7 +206,8 @@ def _rewrite_one_text(*, text, edit_include): return text -def _rewrite_one_file(*, old_filename, new_filename, edit_include): +def _rewrite_one_file(*, old_filename, new_filename, edit_include, + inline_namespace): """Reads in old_filename and write into new_filename with specific alterations as described by _rewrite_one_string(). """ @@ -191,7 +215,8 @@ def _rewrite_one_file(*, old_filename, new_filename, edit_include): with open(old_filename, 'r', encoding='utf-8') as in_file: old_text = in_file.read() - new_text = _rewrite_one_text(text=old_text, edit_include=edit_include) + new_text = _rewrite_one_text(text=old_text, edit_include=edit_include, + inline_namespace=inline_namespace) # Write out the altered file. with open(new_filename, 'w', encoding='utf-8') as out_file: @@ -211,6 +236,9 @@ def _main(): '--edit-include', action='append', default=[], type=_split_pair, metavar='OLD:NEW', help='Project-local include spellings rewrite') + parser.add_argument( + '--no-inline-namespace', dest='inline_namespace', action='store_false', + help='Set visibility directly without an inline namespace wrapper') parser.add_argument( 'rewrite', nargs='+', type=_split_pair, help='Filename pairs to rewrite, given as IN:OUT') @@ -218,6 +246,7 @@ def _main(): for old_filename, new_filename in args.rewrite: _rewrite_one_file( edit_include=args.edit_include, + inline_namespace=args.inline_namespace, old_filename=old_filename, new_filename=new_filename) diff --git a/tools/workspace/vendor_cxx_test.py b/tools/workspace/vendor_cxx_test.py index f23fa20d52ae..7420f1ecefba 100644 --- a/tools/workspace/vendor_cxx_test.py +++ b/tools/workspace/vendor_cxx_test.py @@ -18,14 +18,30 @@ def setUp(self): self._open = 'inline namespace drake_vendor __attribute__ ((visibility ("hidden"))) {' # noqa self._close = '} /* inline namespace drake_vendor */' - def _check(self, old_lines, expected_new_lines): + def _check(self, old_lines, expected_new_lines, inline_namespace=True): """Tests one call to _rewrite_one_text for expected output.""" old_text = '\n'.join(old_lines) + '\n' new_text = _rewrite_one_text( - text=old_text, edit_include=self._edit_include.items()) + text=old_text, edit_include=self._edit_include.items(), + inline_namespace=inline_namespace) expected_new_text = '\n'.join(expected_new_lines) + '\n' self.assertMultiLineEqual(expected_new_text, new_text) + def test_without_inline_namespace(self): + self._check([ + 'namespace foo', + '{', + '}', + ' namespace bar {', + '}', + ], [ + 'namespace foo __attribute__ ((visibility ("hidden")))', + '{', + '}', + 'namespace bar __attribute__ ((visibility ("hidden"))) {', + '}', + ], inline_namespace=False) + def test_comments(self): self._check([ ' // file comment',