Nimterop
Nimterop is a Nim package that aims to make C/C++ interop seamless
Install / Use
/learn @nimterop/NimteropREADME
Nimterop is a Nim package that aims to make C/C++ interop seamless
Most of the wrapping functionality is contained within the toast binary that is built when nimterop is installed and can be used standalone similar to how c2nim can be used today. In addition, nimterop also offers an API to pull in the generated Nim content directly into an application and other functionality that helps in automating the wrapping process. There is also support to statically or dynamically link to system installed libraries or downloading and building them with autoconf or cmake from a Git repo or source archive.
The nimterop wrapping functionality is still limited to C but is constantly expanding. C++ support will be added once most popular C libraries can be wrapped seamlessly. Meanwhile, c2nim can also be used in place of toast with the c2nImport() API call.
The goal is to make interop seamless so nimterop will focus on wrapping headers and not the outright conversion of C/C++ implementation.
Installation
Nimterop can be installed via Nimble:
nimble install nimterop -y
or:
git clone http://github.com/nimterop/nimterop && cd nimterop
nimble develop -y
nimble build -d:danger
This will download and install nimterop in the standard Nimble package location, typically ~/.nimble. Once installed, it can be imported into any Nim program.
Usage
Nimterop can be used in three ways:
- Creating a wrapper file - a
.nimfile that contains calls to the high-level API that can download and build the C library as well as generate the required Nim code to interface with the library. This wrapper file can then be imported into Nim code like any other module and it will be processed at compile time. - Same as the first option except using the
nimFileparam tocImport()to write the generated wrapper to a file during build time just once and then importing that generated wrapper into the application like any other Nim module. - Using the command line
toasttool to generate the Nim code which can then be stored into a file and imported separately.
Any combination of the above is possible - only download, build or wrapping and nimterop avoids imposing any particular workflow.
Refer to CHANGES.md for history and information around breaking changes.
Build API
Creating a wrapper has two parts, the first is to setup the C library. This includes downloading it or finding it if already installed, and building it if applicable. The getHeader() high-level API provides all of this functionality as a convenience. The following .nim wrapper file is an example of using the high-level getHeader() API to perform all building, wrapping and linking automatically:
import nimterop/[build, cimport]
static:
cDebug() # Print wrapper to stdout
const
baseDir = getProjectCacheDir("testwrapper") # Download library within nimcache
getHeader(
"header.h", # The header file to wrap, full path is returned in `headerPath`
giturl = "https://github.com/username/repo", # Git repo URL
dlurl = "https://website.org/download/repo-$1.tar.gz", # Download URL for archive or raw file
conanuri = "repo/$1", # Conan.io URI
jbburi = "repo/$1", # BinaryBuilder.org URI
outdir = baseDir, # Where to download/build/search
conFlags = "--disable-comp --enable-feature", # Flags to pass configure script
cmakeFlags = "-DENABLE_STATIC_LIB=ON" # Flags to pass to Cmake
altNames = "hdr" # Alterate names of the library binary, full path returned in `headerLPath`
)
# Wrap headerPath as returned from getHeader() and link statically
# or dynamically depending on user input
when not isDefined(headerStatic):
cImport(headerPath, recurse = true, dynlib = "headerLPath") # Pass dynlib if not static link
else:
cImport(headerPath, recurse = true)
Module documentation for the build API can be found here. Refer to the tests directory for additional examples on how the library can be used. Also, check out the wiki for a list of all known wrappers that have been created using nimterop. They will provide real world examples of how to wrap libraries. Please do add your project once you are done so that others can benefit from your work.
Download / Search
The above wrapper is generic and allows the end user to control how it works. Note that headerPath is derived from header.h so if you have SDL.h as the argument to getHeader(), it generates SDLPath and SDLLPath and is controlled by -d:SDLStatic, -d:SDLGit and so forth.
- If the library is already installed in
/usr/includethen the-d:headerStddefine to Nim can be used to instructgetHeader()to search forheader.hin the standard system path. - If the library needs to be downloaded, the user can use
-d:headerGitto clone the source from the specified git URL,-d:headerDLto get the source from download URL,-d:headerConanto download from https://conan.io/center or-d:headerJBBto download from https://binarybuilder.org.- The
-d:headerSetVer=X.Y.Zflag can be used to specify which version to download. It is used as the tag name for Git and for DL, Conan and JBB, it replaces$1in the URL if specified.
- The
- If no flag is provided,
getHeader()simply looks for the library inoutdir. The user could use Git submodules or manually download or check-in the library to that directory andgetHeader()will use it directly.
Pre build
getHeader() provides a headerPreBuild() hook that gets called after the library is downloaded but before it is built. This allows for any manipulations of the source files or build scripts before build. archive has such an example.
The build API also includes various compile time helper procs that aid in file manipulation, Cmake shortcuts, library linking, etc. Refer to build for more details.
Build
Nimterop currently supports configure and cmake based building of libraries, with cmake taking precedence if a project supports both. Nimterop verifies that the tool selected is available and notifies the user if any issues are found. Bash is required on Windows for configure and the binary shipped with Git has been tested.
Flags can be specified to these tools via getHeader() or directly via the underlying configure() and cmake() calls. Once the build scripts are ready, getHeader() then calls make(). At every step, getHeader() checks for the presence of created artifacts and does not redo steps that have been successfully completed.
Linking
If -d:headerStatic is specified, getHeader() will return the static library path in headerLPath. The wrapper writer can check for this and call cImport() accordingly as in the example above. If -d:headerStatic is omitted, the dynamic library is returned in headerLPath.
All dependency libraries (supported by Conan and JBB) will be returned in headerLDeps. Static libraries and dependencies are automatically linked using cPassL(). Conan shared libs typically include dependencies compiled in whereas JBB shared libs expect the required dependencies to be in the same location or in LD_LIBRARY_PATH. conanFlags and jbbFlags can be used to skip required dependencies from being downloaded in case another source is preferred. This can be done with skip=pkg1,pkg2 to these flags.
getHeader() searches for libraries based on the header name by default:
libheader.soorlibheader.aon Linuxlibheader.dylibon OSXheader.dll,header.aorheader.libon Windows
If a library has a different header and library binary name, altNames can be used to configure an alternate name of library binary.
- For example, Bzip2 has
bzlib.hbut the library islibbz2.sosoaltNames = "bz2". - In the example above,
altNames = "hdr"sogetHeader()will look forlibhdr.so,hdr.dll, etc. - See bzlib.nim for an example.
lzma.nim is an example of a library that allows both static and dynamic linking.
User control
The -d:xxxYYY Nim define flags have already been described above and can be specified on the command line or in a nim.cfg file. It is also possible to specify them within the wrapper itself using setDefines() if required. Further, all defines, regardless of how they are specified, can be generically checked using isDefined().
If more fine-tuned control is desired over the build process, it is possible to manually control all steps that getHeader() performs by directly using the API provided by build. Note also that there is no requirement to use these APIs to setup the library. Any other established mechanisms can be used to do so any limitations imposed by Nimterop are unintentional and feedback is most welcome.
Wrapper API
Once the C library is setup, the next step is to generate co

