Skip to content

fix(python): statically link and hide bundled HiGHS to avoid symbol clash with highspy (#5246)#5248

Open
aryan5v wants to merge 3 commits into
google:mainfrom
aryan5v:aryan5v/fix-issue-5246
Open

fix(python): statically link and hide bundled HiGHS to avoid symbol clash with highspy (#5246)#5248
aryan5v wants to merge 3 commits into
google:mainfrom
aryan5v:aryan5v/fix-issue-5246

Conversation

@aryan5v

@aryan5v aryan5v commented Jul 8, 2026

Copy link
Copy Markdown

Fixes #5246.

Problem

Any Python process that imports both ortools and highspy (>= 1.15.1) crashes on solver creation:

ImportError: .../ortools/.libs/libortools.so.9: undefined symbol:
  _Z19setLocalOptionValueRK15HighsLogOptions...   # setLocalOptionValue(HighsLogOptions const&, ...)

Reproducer (released wheel):

uv run --no-project --python 3.12 \
  --with 'ortools==9.14.6206' --with 'highspy==1.15.1' \
  python -c 'import highspy; from ortools.linear_solver import pywraplp; print(pywraplp.Solver.CreateSolver("GLOP"))'

This breaks all cvxpy + or-tools users, since cvxpy eagerly co-imports solver backends. It is not HiGHS-specific at the call site — the whole libortools.so fails to load, so GLOP/PDLP/etc. all fail too.

Root cause

On Linux the wheel ships HiGHS as a separate shared library (libhighs.so, in ortools/.libs/), and libortools.so carries undefined HiGHS references resolved at load time. OR-Tools bundles HiGHS v1.12.0; highspy bundles v1.15.1, which renamed/removed setLocalOptionValue. Both copies export HiGHS symbols with default ELF visibility into the global dynamic scope. When highspy is imported first, its symbols occupy global scope; loading libortools.so afterward interposes its HiGHS references onto highspy's incompatible copy, where the symbol no longer exists → undefined symbol at dlopen time. auditwheel repair renames bundled .so files but does not mangle symbols, so it does not prevent the collision.

macOS (two-level namespace) and Windows (explicit OR_EXPORT) are unaffected, so every change is guarded if(UNIX AND NOT APPLE).

Fix

Statically link the bundled HiGHS into libortools.so and localize its symbols, Linux-only:

  • cmake/dependencies/CMakeLists.txt — build the FetchContent HiGHS with BUILD_SHARED_LIBS=OFF (saved/restored around the fetch) so it produces libhighs.a, folded into libortools.so. HiGHS references become internally resolved — no longer undefined, so there is nothing for another HiGHS copy to interpose onto.
  • cmake/cpp.cmake — add target_link_options(ortools PRIVATE "LINKER:--exclude-libs,libhighs.a") so the statically-linked HiGHS symbols become STB_LOCAL in libortools.so's dynamic symbol table (not exported, not interposable). Targeted at the HiGHS archive specifically to avoid over-hiding abseil/protobuf/OR-Tools public symbols.
  • cmake/python.cmake, cmake/java.cmake, cmake/dotnet.cmake — a static library has no SONAME, so the libhighs.so copy-into-package step is skipped when HiGHS is static (via an is_highs_shared type check); the copy command collapses to a no-op.

This is immune to import order and to whatever HiGHS version another package bundles.

Scope

  • HiGHS only. Other bundled solvers (SCIP, CoinOR, GLPK) are not touched — no reported conflict, and CoinOR in particular is a cluster of interdependent shared libs where static-linking would be riskier.
  • CMake only. The PyPI wheels are built via CMake; the Bazel build links HiGHS differently and is not used for the wheels, so it is out of scope here.

Validation (Linux, x86_64, Python 3.12)

Built the unmodified branch and ran the reproducer against the locally-built wheel:

  • Released-wheel bug reproduced in both import orders (baseline).
  • Build succeeds with no patches; HiGHS built static (build/lib/libhighs.a, no libhighs.so*).
  • nm -D shows no exported setLocalOptionValue; readelf -d shows no NEEDED entry for HiGHS; setLocalOptionValue is present only as a local t symbol.
  • Wheel .libs/ contains no libhighs.
  • Both import orders with highspy==1.15.1 now pass; GLOP, PDLP, and HIGHS all solve.

CI note: .github/workflows/amd64_linux_cmake_python.yml exercises the changed Linux wheel build path.

aryan5v added 2 commits July 7, 2026 00:46
…#5246)

When HiGHS is statically linked on Linux, need_unix_highs_lib is false but
the copy command still used BUILD_HIGHS for its IF guard, producing an empty
`cmake -E copy` that fails python_package. Gate copy on the need_*_highs_lib
conditions instead.
@aryan5v aryan5v changed the base branch from stable to main July 8, 2026 08:06
Comment thread cmake/java.cmake Outdated
Comment thread cmake/python.cmake Outdated
@Mizux Mizux self-assigned this Jul 8, 2026
need_unix_highs_lib already requires a shared HiGHS target, so the extra
is_highs_shared wrapper around TARGET_SONAME_FILE is unnecessary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incompatibility between highspy and or-tools

2 participants