conda-forge / conda-forge/gnuplot-feedstock
osx-64 gnuplot 6.0.5: Qt terminal cannot start gnuplot_qt (compiled-in QT_DRIVER_DIR is null-padded after prefix relocation)
- Dominant language
- Shell
- Stars
- 3
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
`set term qt` fails on a fresh `conda create -n gnuplot gnuplot` (osx-64, gnuplot
6.0.5 `hb10a63c_0`, qt-main 5.15.15 `h650d4b3_8`). gnuplot prints
```
Could not start gnuplot_qt with path /usr/local/Caskroom/miniforge/base/envs/gnuplot/libexec/gnuplot/6.0
Did you set environmental variable GNUPLOT_DRIVER_DIR?
```
and then hangs printing `Warning: slow font initialization` forever. The helper
`libexec/gnuplot/6.0/gnuplot_qt` exists, is executable, and runs fine on its own.
Setting `GNUPLOT_DRIVER_DIR=$CONDA_PREFIX/libexec/gnuplot/6.0` makes the Qt
window open in about 3 s, so the package works; only the compiled-in path is
broken. Every other terminal (pngcairo, pdfcairo, postscript) and `help` work
without the variable.
## Reproduce
```sh
conda create -n gnuplot gnuplot # osx-64, conda-forge
conda run -n gnuplot gnuplot -e "set term qt; plot sin(x); pause 1" # hangs, message above
GNUPLOT_DRIVER_DIR=$CONDA_PREFIX/libexec/gnuplot/6.0 \
conda run -n gnuplot gnuplot -e "set term qt; plot sin(x); pause 1" # works
```
## Evidence
**1. The error message itself is truncated.** In `src/qtterminal/qt_term.cpp`,
`execGnuplotQt()` does
```cpp
filename = QT_DRIVER_DIR;
filename += "/";
filename += GNUPLOT_QT;
qt->gnuplot_qtStarted = QProcess::startDetached(filename, ...);
if (!qt->gnuplot_qtStarted)
fprintf(stderr, "Could not start gnuplot_qt with path %s\n", filename.toUtf8().data());
```
so the printed path should end in `/gnuplot_qt`. It ends in `/6.0`. The only
way `%s` can stop there is an embedded NUL in `filename` between the directory
and the `/gnuplot_qt` suffix.
**2. The literal in the binary is null-padded.** Dump at the offset of the
relocated string in `bin/gnuplot`:
```
$ off=$(grep -boa "$CONDA_PREFIX/libexec/gnuplot/6.0" $CONDA_PREFIX/bin/gnuplot | head -1 | cut -d: -f1)
$ dd if=$CONDA_PREFIX/bin/gnuplot bs=1 skip=$off count=120 2>/dev/null | xxd
00000000: 2f75 7372 2f6c 6f63 616c 2f43 6173 6b72 /usr/local/Caskr
00000010: 6f6f 6d2f 6d69 6e69 666f 7267 652f 6261 oom/miniforge/ba
00000020: 7365 2f65 6e76 732f 676e 7570 6c6f 742f se/envs/gnuplot/
00000030: 6c69 6265 7865 632f 676e 7570 6c6f 742f libexec/gnuplot/
00000040: 362e 3000 0000 0000 0000 0000 0000 0000 6.0.............
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
```
Real path: 67 bytes. Literal including the NUL run: 276 bytes
= 255-byte build placeholder + `/libexec/gnuplot/6.0` (20) + terminator.
`conda-meta/gnuplot-6.0.5-hb10a63c_0.json` confirms `bin/gnuplot` was
binary-relocated from the 255-character placeholder
`/Users/runner/miniforge3/conda-bld/gnuplot_1786086290316/_h_env_placehold...`
(`file_mode: binary`).
**3. Why the NULs reach the QString.** `QT_DRIVER_DIR` is a `-D` string
literal (`src/Makefile.am`, `AM_CPPFLAGS ... -DQT_DRIVER_DIR=\"$(QT_DRIVER_DIR)\"`).
`QString::operator=(const char *)` calls `strlen()` on it, and the compiler
constant-folds `strlen` of a literal at build time, when the literal was the
275-character placeholder path. After conda rewrites the bytes, the code still
constructs a 275-character QString, so it contains 208 embedded NULs followed
by `/gnuplot_qt`. `QProcess::startDetached` converts that to a C path, which
stops at the first NUL, i.e. the directory, and exec fails.
The C-side paths (`GNUPLOT_PS_DIR`, `HELPFILE`, `GNUPLOT_JS_DIR`) are padded the
same way (literals of 285, 286, 277 bytes) but work, because they are passed as
NUL-terminated `char *` to `fopen` and friends. Only the Qt terminal builds a
length-carrying string from the literal.
## Suggested fixes
Any one of these:
1. **Ship an activation script** (`etc/conda/activate.d/gnuplot.sh`) exporting
`GNUPLOT_DRIVER_DIR="$CONDA_PREFIX/libexec/gnuplot/6.0"`. Simplest, and it
is the documented gnuplot override. Downside: only helps in activated
shells, not for `conda run` or absolute-path invocations.
2. **Patch `qt_term.cpp`** so the length is computed at run time, e.g.
`filename = QString::fromUtf8(QByteArray(QT_DRIVER_DIR).constData())` is still
foldable; something like a `volatile const char *` or a non-inlined helper
is needed to defeat the folding.
3. **Compile with `-fno-builtin-strlen`** for the qtterminal objects, which
stops the folding without touching the source.
Option 1 is what I have done locally, together with the same export in a
wrapper script; both work.
## Environment
- macOS 26.6.2 (Tahoe), Intel x86_64
- miniforge, conda 26.3.2, channel conda-forge only
- gnuplot 6.0.5 hb10a63c_0 (osx-64), qt-main 5.15.15 h650d4b3_8
- gnuplot 6.0.4 hbf7e6ff_0 not tested; osx-arm64 and linux-64 builds not
tested, but the mechanism is platform-independent and should affect any build
whose relocated prefix is shorter than the placeholder.
Contributor guide
Research direction
Run the two `conda run` reproductions, then inspect `src/qtterminal/qt_term.cpp` and the `QT_DRIVER_DIR` definition in `src/Makefile.am`. Compare the activation-script and build/source approaches, checking the relocated string behavior on the osx-64 package. Done means `set term qt` starts `gnuplot_qt` without `GNUPLOT_DRIVER_DIR`, while the other terminals continue to work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, shell
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100