Skip to content

Add CMake option CUSTOMIO to route stdout/stderr, exit and abort to the program that links SCIP - #236

Open
bnaras wants to merge 2 commits into
scipopt:masterfrom
bnaras:io-redirect
Open

bnaras wants to merge 2 commits into
scipopt:masterfrom
bnaras:io-redirect

Conversation

@bnaras

@bnaras bnaras commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

This is the functionality PR discussed in #194: a CMake option CUSTOMIO with which libscip itself no longer references stdout, stderr, printf(), exit() or abort(). The program that links SCIP supplies a few functions instead. Without the option, the build compiles to the same code as before, apart from the fixes listed below.

Mechanism

  • pub_misc.h, new group "Standard Streams and Termination": SCIPioPrintStdout(), SCIPioPrintStderr(), SCIPioVPrintStdout(), SCIPioVPrintStderr(), SCIPioExit(), SCIPioAbort(), SCIPioGetStdout(), SCIPioGetStderr().
    • Default: macros for printf, fprintf(stderr, ...), vprintf, vfprintf(stderr, ...), exit, abort, stdout, stderr.
    • With SCIP_CUSTOM_IO (config.h, set by -DCUSTOMIO=on): declarations. The two variadic ones are defined in misc.c; the program defines the other six.
  • Linking, option (b) from Proposal: Compile-time I/O abstraction for embedding SCIP as a library #194. With CUSTOMIO, io_default.c (the C library versions of the six), message_default.c, and scipshell.c are left out of libscip and built into the scip executable, which then links and behaves as before. The program that links SCIP defines the six functions and SCIPcreateMessagehdlrDefault(). scip_general.c is unchanged.
  • message.c compares and passes SCIPioGetStdout() / SCIPioGetStderr() where it used stdout / stderr, so the FILE* handed to message handlers keeps its meaning. A program may return stand-in values that its handler recognizes; SCIP only compares them. The default error printer writes through SCIPioPrintStderr() under CUSTOMIO.
  • CUSTOMIO requires SHARED=off. A shared libscip with undefined symbols does not link on macOS or Windows, so CMake stops with a message. If you want shared builds too, this could add -undefined dynamic_lookup on macOS instead.
  • Unit tests, examples and applications link libscip without defining these functions, so with CUSTOMIO their ctest build steps fail to link. make itself is not affected, because they are EXCLUDE_FROM_ALL. Only the check/ tests of the scip executable apply. If you would rather have CMake leave them out under CUSTOMIO, I can add that.
  • The Makefile build is unchanged and does not offer the option. scripts/check_scip_defs.sh builds with every #ifdef name of src/scip defined, which would include SCIP_CUSTOM_IO. The Makefile does not build io_default.c, so its scip binary would lack the six functions; SCIP_CUSTOM_IO is added to the script's EXCLUDES.
  • I did not bump SCIP_VERSION_API; the new functions are declared only with CUSTOMIO, and your history shows the bump in its own commit. Happy to add it if you want it here.

Sites

Existing SCIP routines where one is reachable, the new functions otherwise:

Site Change
message.c stdout/stderr → SCIPioGetStdout()/SCIPioGetStderr(); default error printer
disp.c fflush(stdout) only without CUSTOMIO (the message handler flushes)
expr.c, nlp.c, nlpioracle.c, reader_gms.c file = stdout defaults → SCIPioGetStdout()
reader_opb.c SCIPprintCons(..., stderr) → SCIPioGetStderr()
interrupt.c, dialog.c, matrix.c, stat.c, rational.cpp printf/vprintf/putchar/std::cout/exit → SCIPio* (SCIPrationalPrint() keeps std::cout without CUSTOMIO)
blockmemshell/memory.c printInfo and BMSprintBufferMemory() → SCIPioPrintStdout(), only in the WITH_SCIPDEF branch
tclique/tclique_def.h, xml/xmldef.h, xml/xmlparse.c printf/fprintf(stderr, ...)/abort → SCIPio*; pub_misc.h is included by the three tclique .c files, because tclique.h includes tclique_def.h inside extern "C"
dialog_default.c two printf → SCIPdialogMessage()
objscip/objconshdlr.h default scip_print() → SCIPinfoMessage(scip, file, ...) instead of fprintf(stdout, ...)

dialog_default.c and objconshdlr.h now print through the message handler, so these lines respect quiet mode. Everything else prints as before.

Fixes on their own merits:

  • SCIPstatDebugMessagePrint() passed its va_list to printf(); it now uses vprintf (also on v101-bugfix).
  • cons_nonlinear.c: sprintf → SCIPsnprintf.
  • tpi_openmp.c: printf("err1") → SCIPerrorMessage() with the job id.
  • dijkstraGraphIsValid() returns FALSE instead of calling abort(). It is only called inside assert(), like dijkstraHeapIsValid(), which already returns FALSE.

Not changed

With CUSTOMIO, these objects of libscip still reference flagged symbols:

  • nauty/*.c (exit, stderr, stdout) and symmetry/compute_symmetry_sassy_nauty.cpp (std::cout, from the dejavu headers): vendored, left untouched as you preferred. Is a small follow-up for nauty's exit calls of interest?
  • lpi/lpi_spx.cpp (abort, std::cout, std::cerr): instantiated from SoPlex headers. I will open an issue at SoPlex.

Verification

R's own compiled-code check (tools:::so_symbol_names_table against nm -Pg undefined symbols), on:

  • macOS arm64, Apple clang, TPI=none;
  • Ubuntu 24.04 gcc 13, TPI=omp, _FORTIFY_SOURCE=3;
  • Fedora clang 23 / libc++ 23 (CRAN's configuration), TPI=omp, LPS=none.

Results:

  • Without CUSTOMIO: flagged symbols per object are the same as for plain master 723f18f2de, except the three removed by the fixes (dijkstra.c abort, cons_nonlinear.c sprintf, dialog_default.c printf). On macOS, 429 of the 433 libscip objects have the same disassembly as with plain master (addresses and line numbers masked); the other four are dialog_default.c, stat.c, cons_nonlinear.c and dijkstra.c.
  • With CUSTOMIO: libscip's flagged objects are only those under "Not changed". The CMake builds on the three platforms above, with and without CUSTOMIO, compile without warnings.
  • scip executable, with vs without CUSTOMIO: same output on bell5.mps, p0033.osil, MANN_a9.clq.lp, a missing file, a malformed OSiL file (stderr) and commands from stdin, apart from timings.
  • An embedder (the R package's C sources plus R implementations of the six functions and SCIPcreateMessagehdlrDefault()) links against the CUSTOMIO libscip with nothing unresolved and solves a MIP, on macOS and Linux.
  • The Makefile build (default) still builds.
  • ctest (macOS, BUILD_TESTING=on, the R package's configuration): without CUSTOMIO, 1539 of 1542 pass; the other three (Issue-seed_1-4304.nl, MINLP-readertest-nl-bip_cross_min.10.10.10.1.pip, unittest-cons-superindicator-slack) fail the same way on plain master with this configuration, which has AMPL=off and ZLIB=off. With CUSTOMIO, the check/ tests pass apart from the same two .nl tests; the 168 build steps of unit tests, examples and applications fail to link on exactly the seven functions (plus SCIPprocessShellArguments()/SCIPrunShell() where they use scipshell.c), and the 272 tests that depend on them do not run.
  • scripts/check_scip_defs.sh's build matrix (LPS=none; gcc, g++, clang, clang++; dbg and opt; gcc 13 and clang 23 on Linux): same make exit status, errors, warnings and number of compiled objects as plain master. Plain master does not compile as C++ (with CMake CXXONLY=on on macOS, 10 errors in six files, for example cons_pseudoboolean.c:239), so these runs used make -k and compared the results instead of stopping at the first error. CMake CXXONLY=on builds on macOS, with and without CUSTOMIO, have the same errors as plain master and the same warnings except the sprintf deprecation warning that the cons_nonlinear.c fix removes; io_default.c compiles as C++ without warnings.
  • scripts/check_scip_style.prl reports nothing new in the changed files.
  • Debug builds (macOS, with and without CUSTOMIO) compile without warnings and solve p0033.osil. They additionally reference assert almost everywhere, plus two NDEBUG-only sites, which I left alone: stdout inside assert() in sepa_flower.c and abort() in intervalarith.c.

Symbols for a check in your CI

These are the C and C++ rows of R's table (tools:::so_symbol_names_table), dumped from R on each platform. CRAN reports an object file if any of these names is among its undefined symbols (nm -Pg, type U).

Language Symbol macOS (R 4.6.1, arm64) Linux (R 4.6.1, aarch64) Linux (R-devel 2026-10-02, x86_64)
C _exit __exit _exit _exit
C _Exit __Exit, __exit _Exit _Exit
C abort _abort abort abort
C assert ___assert_rtn __assert_fail_base __assert_fail_base
C exit _exit exit exit
C printf _printf, _puts printf, __printf_chk, puts printf, __printf_chk, puts
C putchar _putchar putchar putchar
C puts _puts puts puts
C rand _rand rand rand
C rand_r _rand_r rand_r rand_r
C random _random random random
C sprintf _sprintf, ___sprintf_chk sprintf, __sprintf_chk sprintf, __sprintf_chk
C srand _srand srand srand
C srand48 _srand48 srand48 srand48
C srandom _srandom srandom srandom
C srandom_r srandom_r srandom_r
C stderr ___stderrp stderr stderr
C stdout ___stdoutp stdout stdout
C vprintf _vprintf vprintf, __vprintf_chk vprintf, __vprintf_chk
C vsprintf _vsprintf, ___vsprintf_chk vsprintf, __vsprintf_chk vsprintf, __vsprintf_chk
C++ std::cerr __ZNSt3__14cerrE _ZSt4cerr, _ZNSt3__14cerrE _ZSt4cerr, _ZNSt3__14cerrE
C++ std::cout __ZNSt3__14coutE _ZSt4cout, _ZNSt3__14coutE _ZSt4cout, _ZNSt3__14coutE

Notes for such a check:

  • Compilers turn printf into puts/putchar and, with _FORTIFY_SOURCE, into __printf_chk (gcc) or __vprintf_chk (clang), so the check has to look at nm output, not at the sources.
  • fputs/fprintf/fflush on a FILE* that was passed in are not on the list; naming stdout/stderr is.
  • assert is on the list, so the check should use a build with NDEBUG (CMake's default Release type). All builds above are Release builds unless marked Debug.
  • With CUSTOMIO, the expected result for libscip today is the objects listed under "Not changed".

Base: master. The touched files differ little between master and v10-minor, so I can retarget to v10-minor if you would rather have it in 10.2.

- SCIPstatDebugMessagePrint() passed its va_list to printf(); use vprintf()
- cons_nonlinear.c: use SCIPsnprintf() instead of sprintf()
- tpi_openmp.c: report an unknown job id with SCIPerrorMessage() instead of
  printing "err1"
- dijkstraGraphIsValid() returns FALSE for an invalid graph instead of calling
  abort(); it is only called inside assert(), like dijkstraHeapIsValid()
… the embedding program

Code that cannot use a message handler now writes to stdout and stderr and
terminates through SCIPioPrintStdout(), SCIPioPrintStderr(),
SCIPioVPrintStdout(), SCIPioVPrintStderr(), SCIPioExit() and SCIPioAbort(),
and message.c uses SCIPioGetStdout() and SCIPioGetStderr() where it used
stdout and stderr. By default these are macros for the C library, so default
builds compile to the same code as before.

With CUSTOMIO (SCIP_CUSTOM_IO in config.h) they are functions that the
program linking SCIP defines, together with SCIPcreateMessagehdlrDefault().
io_default.c (their C library implementations), message_default.c and
scipshell.c are then left out of the library and built into the scip
executable. CUSTOMIO requires SHARED=off.

Where a SCIP message routine is reachable it is used instead:
dialog_default.c uses SCIPdialogMessage(), and the default scip_print() of
ObjConshdlr uses SCIPinfoMessage().

The tclique sources include pub_misc.h themselves, not through
tclique_def.h, because tclique.h includes tclique_def.h inside extern "C".

scripts/check_scip_defs.sh excludes SCIP_CUSTOM_IO: the Makefile build does
not provide the functions.

Vendored nauty and dejavu and the SoPlex code instantiated in lpi_spx.cpp are
unchanged.
bnaras added a commit to bnaras/scip that referenced this pull request Oct 20, 2026
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.

1 participant