I'm trying to use ncurses in a library using Swift Package Manager and I'd like to use a specific version of ncurses, not the one included in OS X.
To do so I installed a more recent version (6.1) using Homebrew.
This is how my Package.swift
looks like:
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "NcursesExample",
products: [
.executable(name: "NcursesExample", targets: ["NcursesExample"]),
],
dependencies: [
],
targets: [
.systemLibrary(name: "Cncurses"),
.target(name: "NcursesExample", dependencies: ["Cncurses"]),
]
)
Under the Sources directory I have a subdirectory for Cncurses containing a module.modulemap
and shim.h
files:
module.modulemap
module Cncurses {
header "shim.h"
link "ncurses"
export *
}
shim.h
#include "/usr/local/Cellar/ncurses/6.1/include/ncurses.h"
However, when compiling I get several errors complaining about conflicting types, apparently because ncurses is also provided by the macOS SDK:
shim.h:1:10: note: in file included from shim.h:1:
#include "/usr/local/Cellar/ncurses/6.1/include/ncurses.h"
^
/usr/local/Cellar/ncurses/6.1/include/ncurses.h:60:10: error: 'ncursesw/ncurses_dll.h' file not found with <angled> include; use "quotes" instead
#include <ncursesw/ncurses_dll.h>
^
<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "shim.h"
^
shim.h:1:10: note: in file included from shim.h:1:
#include "/usr/local/Cellar/ncurses/6.1/include/ncurses.h"
^
/usr/local/Cellar/ncurses/6.1/include/ncurses.h:674:45: error: conflicting types for 'keyname'
extern NCURSES_EXPORT(NCURSES_CONST char *) keyname (int); /* implemented */
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/include/curses.h:598:45: note: previous declaration is here
extern NCURSES_EXPORT(NCURSES_CONST char *) keyname (int); /* implemented */
...
I'm trying to compile the package using:
swift build -Xcc -I/usr/local/Cellar/ncurses/6.1/include/ -Xlinker -L/usr/local/Cellar/ncurses/6.1/lib
I also went down the route of specifying the pkgConfig
on the package definition, with the same result. Can someone help?
FYI It's worth mentioning that upon ncurses installation through Homebrew I get the following warning as ncurses is already provided by OS X:
ncurses is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
If you need to have ncurses first in your PATH run:
echo 'export PATH="/usr/local/opt/ncurses/bin:$PATH"' >> ~/.zshrc
For compilers to find ncurses you may need to set:
export LDFLAGS="-L/usr/local/opt/ncurses/lib"
export CPPFLAGS="-I/usr/local/opt/ncurses/include"
For pkg-config to find ncurses you may need to set:
export PKG_CONFIG_PATH="/usr/local/opt/ncurses/lib/pkgconfig"
Pkgconfig for ncurses looks like this
# pkg-config file generated by gen-pkgconfig
# vile:makemode
prefix=/usr/local/Cellar/ncurses/6.1
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include/ncursesw
abi_version=6
major_version=6
version=6.1.20180127
Name: ncursesw
Description: ncurses 6.1 library
Version: ${version}
URL: https://invisible-island.net/ncurses
Requires.private:
Libs: -L${libdir} -lncursesw
Libs.private:
Cflags: -D_DARWIN_C_SOURCE -I/usr/local/Cellar/ncurses/6.1/include -I${includedir}
Problem
The main problem are conflicts of the header files, since ncurses is also supplied in /Applications/Xcode.app/.../MacOSX10.14.sdk/usr/include.
A common pure C solution in such situations is to just specify the custom include and lib directories with -I repective -L and it would work, see my answer regarding C ncurses here: https://stackoverflow.com/a/56623033/2331445
This approach does not seem to work with the Swift package manager. But that doesn't mean it's not possible with a little effort.
Possible Solution
We need to make sure that the ncurses header files provided by the macOS SDK are ignored. We can do this by specifying the -Xcc -D__NCURSES_H parameter for the swift build command.
This works because in the header file, there is this typical:
#ifndef __NCURSES_H
#define __NCURSES_H
...
#endif
The problem, of course, is that our custom installation of ncurses using Brew is also affected. But we can work around it:
#include <ncursesw/unctrl.h>
the form '#include "ncursesw/unctrl.h"' is usedThis can actually be done with the following command line commands:
cd Sources/Cncurses
cp -r /usr/local/Cellar/ncurses/6.1/include include
find . -name '*.h' -exec sed -i '' 's/__NCURSES_H/__CNCURSES_H/g' {} \;
find . -name '*.h' -exec sed -i '' -E -e "s/<(.*(`find . -name '*.h' -exec basename {} \; | paste -sd "|" -`))>/\"\1\"/g" {} \;
The last statement may require some explanation. With the help of an echo command, you can look at the generated sed expression, i.e. if you execute
echo "s/<(.*(`find . -name '*.h' -exec basename {} \; | paste -sd "|" -`))>/\"\1\"/g"
you get the following output:
s/<(.*(termcap.h|form.h|term.h|panel.h|ncurses.h|termcap.h|cursesp.h|cursesf.h|etip.h|form.h|cursesw.h|nc_tparm.h|unctrl.h|cursesapp.h|term.h|cursslk.h|panel.h|ncurses.h|tic.h|eti.h|ncurses_dll.h|term_entry.h|menu.h|cursesm.h|curses.h|curses.h|cncurses.h))>/"\1"/g
As you can see, it searches and replaces only local available include files.
Test
For a test we need a simple ncurses example program. It should be built and we should make sure that the correct version of the library is used.
module.modulemap
My header file is called cncurses.h. The module.modulemap looks like this:
module cncurses [system]
{
umbrella header "cncurses.h"
link "ncurses"
export *
}
cncurses.h
cncurses.h is a one-liner, it imports our copied and customized ncurses.h file from our local include folder:
#include "include/ncurses.h"
main.swift
In the NcursesExample folder we have main.swift where we have a simple cncurses swift app:
import cncurses
initscr()
curs_set(0)
move(5, 10)
addstr("NCURSES")
move(10, 10)
addstr("Hello World!")
refresh()
select(0, nil, nil, nil, nil)
Package.swift
Please note here the pkgConfig: "ncurses"
in the systemLibrary targets:
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "NcursesExample",
dependencies: [
],
targets: [
.systemLibrary(name: "cncurses", pkgConfig: "ncurses"),
.target(name: "NcursesExample", dependencies: ["cncurses"]),
.testTarget(
name: "NcursesExampleTests",
dependencies: ["NcursesExample"]),
]
)
Build
For pkg-config to do its job properly, we must first call the following:
export PKG_CONFIG_PATH="/usr/local/opt/ncurses/lib/pkgconfig"
Finally we initiate the build with:
swift build -Xcc -D__NCURSES_H
So first we should test if the correct ncurses lib was used. We can do that with:
otool -L .build/x86_64-apple-macosx/debug/NcursesExample
Among other lines, the output contains this:
/usr/local/opt/ncurses/lib/libncursesw.6.dylib (compatibility version 6.0.0, current version 6.0.0)
which looks promising.
Finally calling the binary:
Xcode Project
If you want to generate a Xcode project, use the following command:
swift package generate-xcodeproj
Then load the project in Xcode and
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With