I am porting my tiny console UI toolkit library that I wrote for C# to Swift which is built on top of ncurses [1]. The Darwin.ncurses library does not seem to surface enough of the API from ncurses (character input, and various definitions) and I have struggled to find a way of doing my own binding for ncurses so far as the information seems to have changed significantly over time.
There are a number of github projects called CNCurses with many variations [2] but none of them appear to compile anymore, with errors like:
error: the package does not contain a buildable target
I suspect the Darwin.ncurses is lacking methods purely by how the binding was produced - some macros are missing, some methods are missing -likely a missing define at compile time?
What I was hoping was to produce some sort of projection from the C API in ncurses.h to Swift that I could consume, and I could drive that binding by providing the proper compile time definitions, am I on the right path here?
Awesome! I love seeing more command-line packages.
There are two potential issues here.
Unfortunately, the Darwin ncurses is very old. The version in Xcode 10.2 is 5.7, which hails from 2008. Do you know if this old of a version is suitable for your needs? Would it have the declarations you expect?
The other potential issue is that the declarations are somehow un-importable into Swift. This is pretty rare, so it'd be interesting to see what's going on. We can often improve the import story through extra attributes via something like an apinotes file.
Can you share your progress so far? I'd like to take a look.
The problem with Darwin.ncurses is that the declarations are missing - perhaps because Swift was not able to import them? I suspect the real reason is that whoever did the import did not specify the _XOPEN_SOURCE_EXTENDED define, hence keeping a number of APIs from being bound.
I have seen lots of folks have that CNCurses variation, but none of them build. What I am looking for is for some help to get one of these working:
Last time I checked, the Darwin modulemap was missing the 'menu' and 'panel' headers, so you have to make your own. Refer to this post: Ncurses on Linux with Swift - #2 by Karl
That error message you posted makes it sound like a changes to the manifest format made it so SwiftPM could no longer able to understand the package, but I didn't investigate in depth.
Thanks Karl - I did try that post, it was my first attempts, but could not get anywhere. "swift build" fails with the error described at the top of this post. That is where I am stuck.
I am fine without menu and panels, my library rolls its own.
Could you provide an example of a specific declaration that is missing and I can see what happened to it?
_XOPEN_SOURCE_EXTENDED seems like a candidate, as it does guard many of the "wch" declarations. @rintaro or @Michael_Gottesman, do you know who would be familiar with the flags we pass for the Darwin module? Any idea why we might be defining or not defining it?
That claims to be Linux only, but I don't see any reason it must be so. @Aciid, if I try to build that it complains:
CNCURSES: error: package at 'CNCURSES' is using Swift tools version 3.1.0 which is no longer supported; use 4.0.0 or newer instead
How can the package update? Also, I see that it has a hard-coded header path, which doesn't correspond to where curses.h from Xcode (old 5.7) or a new one from home-brew (6.1) would live. How can this be made more flexible?
I haven't seen @migueldeicaza's error message, @Aciid, any idea what that means?
You're getting this because you can't really build a system library target. They only act as a bridging layer to import C types into Swift. Once you setup a system library, you need some buildable target (library, executable, unit test) in order to use it. I don't think you can use the brew version of ncurses in Swift because that gets in a conflict with the one in SDK. Since you can already import ncurses using the Darwin module, there is no need of creating a system library on Mac. The examples you quoted are useful for linux since Glibc's modulemap probably doesn't include ncurses.h.
The one I was looking for was get_wch, or any of the variations for wide-character keyboard input. Those were the ones behind that _XOPEN_SOURCE_EXTENDED define.
Earlier I found that some of the macros were also missing, I just went and copied the values that I had from my C# binding (A_NORMAL, A_STANDOUT, and the rest of the attributes).
This is my intermediate workaround to unblock my work:
typealias get_wch_def = @convention(c) (UnsafeMutablePointer<Int32>) -> Int
var get_wch_fn : get_wch_def? = nil
let rtld_default = UnsafeMutableRawPointer(bitPattern: -2)
...
let get_wch_ptr = dlsym (rtld_default, "get_wch")
get_wch_fn = unsafeBitCast(get_wch_ptr, to: get_wch_def.self)
Thanks for the guidance @Aciid - that sheds some light into the problem.
A newer version of ncurses would be convenient to bind, but it is not necessary for my UI toolkit.
My follow up question is: if I have a system-library "package" in this form, how can I consume it and the types it might bring in into Xcode? I have also struggled to find an answer to that, would love to reuse any of these CNCurses definition as they are - I believe they all rely on the compilation flags to know where to resolve ncurses.h from.
As I said, you can't use these ncurses system library packages (which are probably meant for curses installed with brew) on Mac because they get into a conflict with the one that's already installed in the SDK.
Ah, I am able get access to those APIs by combining system library target (non-brew) with the recent build settings feature that landed in Swift 5. The trick is to define the _XOPEN_SOURCE_EXTENDED macro in cSettings. Here is a sample package that demonstrates this: https://github.com/aciidb0mb3r/swift-package-manager/tree/curses/Examples/ncurses
Earlier I found that some of the macros were also missing …
A common cause of this is that the macro is too complex for the Swift C importer to deal with. I’m not a curses expert, so let’s pick an example from something more generic, like stat. In this program:
print(S_IRUSR)
print(DEFFILEMODE)
the first line compiles and the second line doesn’t. That’s because the Swift C importer can deal with this:
I prefer not to hard-code macro constants in my source code, so I generally resolve this by writing C code that ‘exports’ the macro in a format that Swift can understand. For example:
// In my C header…
extern mode_t defaultFileMode;
// In my C implementation…
mode_t defaultFileMode = DEFFILEMODE;