1{
2 lib,
3 stdenv,
4 fetchurl,
5 buildPackages,
6 updateAutotoolsGnuConfigScriptsHook,
7 ncurses,
8 pkg-config,
9 abiVersion ? "6",
10 enableStatic ? stdenv.hostPlatform.isStatic,
11 withCxx ? !stdenv.hostPlatform.useAndroidPrebuilt,
12 mouseSupport ? false,
13 gpm,
14 withTermlib ? false,
15 unicodeSupport ? true,
16 testers,
17 binlore,
18}:
19
20stdenv.mkDerivation (finalAttrs: {
21 version = "6.5";
22 pname = "ncurses" + lib.optionalString (abiVersion == "5") "-abi5-compat";
23
24 src = fetchurl {
25 url = "https://invisible-island.net/archives/ncurses/ncurses-${finalAttrs.version}.tar.gz";
26 hash = "sha256-E22RvCaamleF5fnpgLx2q1dCj2BM4+WlqQzrx2eXHMY=";
27 };
28
29 outputs = [
30 "out"
31 "dev"
32 "man"
33 ];
34 setOutputFlags = false; # some aren't supported
35
36 patches = [
37 # linux-gnuabielfv{1,2} is not in ncurses' list of GNU-ish targets (or smth like that?).
38 # Causes some defines (_XOPEN_SOURCE=600, _DEFAULT_SOURCE) to not get set, so wcwidth is not exposed by system headers, which causes a FTBFS.
39 # Reported and fix submitted to upstream in https://lists.gnu.org/archive/html/bug-ncurses/2025-07/msg00040.html
40 # Backported to the 6.5 release (dropped some hunks for code that isn't in this release yet)
41 ./1001-ncurses-Support-gnuabielfv1-2.patch
42 ];
43
44 postPatch = ''
45 sed -i '1i #include <stdbool.h>' include/curses.h.in
46 '';
47
48 # see other isOpenBSD clause below
49 configurePlatforms =
50 if stdenv.hostPlatform.isOpenBSD then
51 [ "build" ]
52 else
53 [
54 "build"
55 "host"
56 ];
57
58 configureFlags = [
59 (lib.withFeature (!enableStatic) "shared")
60 "--without-debug"
61 "--enable-pc-files"
62 "--enable-symlinks"
63 "--with-manpage-format=normal"
64 "--disable-stripping"
65 "--with-versioned-syms"
66 ]
67 ++ lib.optional unicodeSupport "--enable-widec"
68 ++ lib.optional (!withCxx) "--without-cxx"
69 ++ lib.optional (abiVersion == "5") "--with-abi-version=5"
70 ++ lib.optional stdenv.hostPlatform.isNetBSD "--enable-rpath"
71 ++ lib.optional withTermlib "--with-termlib"
72 ++ lib.optionals stdenv.hostPlatform.isWindows [
73 "--enable-sp-funcs"
74 "--enable-term-driver"
75 ]
76 ++ lib.optionals (stdenv.hostPlatform.isUnix && enableStatic) [
77 # For static binaries, the point is to have a standalone binary with
78 # minimum dependencies. So here we make sure that binaries using this
79 # package won't depend on a terminfo database located in the Nix store.
80 "--with-terminfo-dirs=${
81 lib.concatStringsSep ":" [
82 "/etc/terminfo" # Debian, Fedora, Gentoo
83 "/lib/terminfo" # Debian
84 "/usr/share/terminfo" # upstream default, probably all FHS-based distros
85 "/run/current-system/sw/share/terminfo" # NixOS
86 ]
87 }"
88 ]
89 ++ lib.optionals (stdenv.buildPlatform != stdenv.hostPlatform) [
90 "--with-build-cc=${buildPackages.stdenv.cc}/bin/${buildPackages.stdenv.cc.targetPrefix}cc"
91 ]
92 ++ (lib.optionals (stdenv.cc.bintools.isLLVM && lib.versionAtLeast stdenv.cc.bintools.version "17")
93 [
94 # lld17+ passes `--no-undefined-version` by default and makes this a hard
95 # error; ncurses' `resulting.map` version script references symbols that
96 # aren't present.
97 #
98 # See: https://lists.gnu.org/archive/html/bug-ncurses/2024-05/msg00086.html
99 #
100 # For now we allow this with `--undefined-version`:
101 "LDFLAGS=-Wl,--undefined-version"
102 ]
103 )
104 ++ lib.optionals stdenv.hostPlatform.isOpenBSD [
105 # If you don't specify the version number in the host specification, a branch gets taken in configure
106 # which assumes that your openbsd is from the 90s, leading to a truly awful compiler/linker configuration.
107 # No, autoreconfHook doesn't work.
108 "--host=${stdenv.hostPlatform.config}${stdenv.cc.libc.version}"
109 ]
110 # Without this override, the upstream configure system results in
111 #
112 # typedef unsigned char NCURSES_BOOL;
113 # #define bool NCURSES_BOOL;
114 #
115 # Which breaks C++ bindings:
116 #
117 # > /nix/store/[...]-gcc-15.1.0/include/c++/15.1.0/cstddef:81:21: error: redefinition of 'struct std::__byte_operand<unsigned char>'
118 # > 81 | template<> struct __byte_operand<unsigned char> { using __type = byte; };
119 # > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
120 # > /nix/store/[...]-gcc-15.1.0/include/c++/15.1.0/cstddef:78:21: note: previous definition of 'struct std::__byte_operand<unsigned char>'
121 # > 78 | template<> struct __byte_operand<bool> { using __type = byte; };
122 #
123 ++ [
124 "cf_cv_type_of_bool=bool"
125 ];
126
127 # Only the C compiler, and explicitly not C++ compiler needs this flag on solaris:
128 CFLAGS = lib.optionalString stdenv.hostPlatform.isSunOS "-D_XOPEN_SOURCE_EXTENDED";
129
130 strictDeps = true;
131
132 nativeBuildInputs = [
133 updateAutotoolsGnuConfigScriptsHook
134 pkg-config
135 ]
136 ++ lib.optionals (stdenv.buildPlatform != stdenv.hostPlatform) [
137 # for `tic`, build already depends on for build `cc` so it's weird the build doesn't just build `tic`.
138 ncurses
139 ];
140
141 buildInputs = lib.optional (mouseSupport && stdenv.hostPlatform.isLinux) gpm;
142
143 preConfigure = ''
144 export PKG_CONFIG_LIBDIR="$dev/lib/pkgconfig"
145 mkdir -p "$PKG_CONFIG_LIBDIR"
146 configureFlagsArray+=(
147 "--libdir=$out/lib"
148 "--includedir=$dev/include"
149 "--bindir=$dev/bin"
150 "--mandir=$man/share/man"
151 "--with-pkg-config-libdir=$PKG_CONFIG_LIBDIR"
152 )
153 ''
154 + lib.optionalString stdenv.hostPlatform.isSunOS ''
155 sed -i -e '/-D__EXTENSIONS__/ s/-D_XOPEN_SOURCE=\$cf_XOPEN_SOURCE//' \
156 -e '/CPPFLAGS="$CPPFLAGS/s/ -D_XOPEN_SOURCE_EXTENDED//' \
157 configure
158 CFLAGS=-D_XOPEN_SOURCE_EXTENDED
159 '';
160
161 enableParallelBuilding = true;
162
163 doCheck = false;
164
165 postFixup =
166 let
167 abiVersion-extension =
168 if stdenv.hostPlatform.isDarwin then "${abiVersion}.$dylibtype" else "$dylibtype.${abiVersion}";
169 in
170 ''
171 # Determine what suffixes our libraries have
172 suffix="$(awk -F': ' 'f{print $3; f=0} /default library suffix/{f=1}' config.log)"
173 ''
174 # When building a wide-character (Unicode) build, create backward
175 # compatibility links from the the "normal" libraries to the
176 # wide-character libraries (e.g. libncurses.so to libncursesw.so).
177 + lib.optionalString unicodeSupport ''
178 libs="$(ls $dev/lib/pkgconfig | tr ' ' '\n' | sed "s,\(.*\)$suffix\.pc,\1,g")"
179 suffixes="$(echo "$suffix" | awk '{for (i=1; i < length($0); i++) {x=substr($0, i+1, length($0)-i); print x}}')"
180
181 # Get the path to the config util
182 cfg=$(basename $dev/bin/ncurses*-config)
183
184 # symlink the full suffixed include directory
185 ln -svf . $dev/include/ncurses$suffix
186
187 for newsuffix in $suffixes ""; do
188 # Create a non-abi versioned config util links
189 ln -svf $cfg $dev/bin/ncurses$newsuffix-config
190
191 # Allow for end users who #include <ncurses?w/*.h>
192 ln -svf . $dev/include/ncurses$newsuffix
193
194 for library in $libs; do
195 for dylibtype in so dll dylib; do
196 if [ -e "$out/lib/lib''${library}$suffix.$dylibtype" ]; then
197 ln -svf lib''${library}$suffix.$dylibtype $out/lib/lib$library$newsuffix.$dylibtype
198 ln -svf lib''${library}$suffix.${abiVersion-extension} $out/lib/lib$library$newsuffix.${abiVersion-extension}
199 if [ "ncurses" = "$library" ]
200 then
201 # make libtinfo symlinks
202 ln -svf lib''${library}$suffix.$dylibtype $out/lib/libtinfo$newsuffix.$dylibtype
203 ln -svf lib''${library}$suffix.${abiVersion-extension} $out/lib/libtinfo$newsuffix.${abiVersion-extension}
204 fi
205 fi
206 done
207 for statictype in a dll.a la; do
208 if [ -e "$out/lib/lib''${library}$suffix.$statictype" ]; then
209 ln -svf lib''${library}$suffix.$statictype $out/lib/lib$library$newsuffix.$statictype
210 if [ "ncurses" = "$library" ]
211 then
212 # make libtinfo symlinks
213 ln -svf lib''${library}$suffix.$statictype $out/lib/libtinfo$newsuffix.$statictype
214 fi
215 fi
216 done
217 ln -svf ''${library}$suffix.pc $dev/lib/pkgconfig/$library$newsuffix.pc
218 done
219 done
220 ''
221 # Unconditional patches. Leading newline is to avoid mass rebuilds.
222 + ''
223
224 # add pkg-config aliases for libraries that are built-in to libncurses(w)
225 for library in tinfo tic; do
226 for suffix in "" ${lib.optionalString unicodeSupport "w"}; do
227 ln -svf ncurses$suffix.pc $dev/lib/pkgconfig/$library$suffix.pc
228 done
229 done
230
231 # move some utilities to $bin
232 # these programs are used at runtime and don't really belong in $dev
233 moveToOutput "bin/clear" "$out"
234 moveToOutput "bin/reset" "$out"
235 moveToOutput "bin/tabs" "$out"
236 moveToOutput "bin/tic" "$out"
237 moveToOutput "bin/tput" "$out"
238 moveToOutput "bin/tset" "$out"
239 moveToOutput "bin/captoinfo" "$out"
240 moveToOutput "bin/infotocap" "$out"
241 moveToOutput "bin/infocmp" "$out"
242 '';
243
244 preFixup = lib.optionalString (!stdenv.hostPlatform.isCygwin && !enableStatic) ''
245 rm "$out"/lib/*.a
246 '';
247
248 # I'm not very familiar with ncurses, but it looks like most of the
249 # exec here will run hard-coded executables. There's one that is
250 # dynamic, but it looks like it only comes from executing a terminfo
251 # file, so I think it isn't going to be under user control via CLI?
252 # Happy to have someone help nail this down in either direction!
253 # The "capability" is 'iprog', and I could only find 1 real example:
254 # https://invisible-island.net/ncurses/terminfo.ti.html#tic-linux-s
255 passthru.binlore.out = binlore.synthesize ncurses ''
256 execer cannot bin/{reset,tput,tset}
257 '';
258
259 meta = with lib; {
260 homepage = "https://www.gnu.org/software/ncurses/";
261 description = "Free software emulation of curses in SVR4 and more";
262 longDescription = ''
263 The Ncurses (new curses) library is a free software emulation of curses in
264 System V Release 4.0, and more. It uses Terminfo format, supports pads and
265 color and multiple highlights and forms characters and function-key
266 mapping, and has all the other SYSV-curses enhancements over BSD Curses.
267
268 The ncurses code was developed under GNU/Linux. It has been in use for
269 some time with OpenBSD as the system curses library, and on FreeBSD and
270 NetBSD as an external package. It should port easily to any
271 ANSI/POSIX-conforming UNIX. It has even been ported to OS/2 Warp!
272 '';
273 license = licenses.mit;
274 pkgConfigModules =
275 let
276 base = [
277 "form"
278 "menu"
279 "ncurses"
280 "panel"
281 ]
282 ++ lib.optional withCxx "ncurses++";
283 in
284 base ++ lib.optionals unicodeSupport (map (p: p + "w") base);
285 platforms = platforms.all;
286 };
287
288 passthru = {
289 ldflags = "-lncurses";
290 inherit unicodeSupport abiVersion;
291 tests.pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage;
292 };
293})