This commit is contained in:
genotrance 2019-10-03 20:40:05 -05:00 committed by GitHub
commit dc222c6011
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1285 additions and 485 deletions

View file

@ -2,29 +2,33 @@ os:
- linux
- osx
addons:
apt:
packages:
- autopoint
language: c
env:
- BRANCH=0.19.6
- BRANCH=0.20.0
- BRANCH=0.20.2
- BRANCH=1.0.0
- BRANCH=devel
cache:
directories:
- "$HOME/.choosenim/toolchains/nim-0.19.6"
- "$HOME/.choosenim/toolchains/nim-0.20.0"
- "$HOME/.choosenim/toolchains/nim-0.20.2"
- "$HOME/.choosenim/toolchains/nim-1.0.0"
install:
# `set -u` failed for ubuntu: /home/travis/.travis/job_stages: line 107: secure: unbound variable
- set -e
- export CHOOSENIM_CHOOSE_VERSION=$BRANCH
- |
curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
sh init.sh -y
- export PATH=$HOME/.nimble/bin:$PATH
- export PATH="$HOME/.nimble/bin:/usr/local/opt/gettext/bin:$PATH"
script:
- set -e
- nimble --verbose install -y
- nimble --verbose test
- nimble --verbose --nimbleDir:`pwd`/build/fakenimble install nimterop -y

View file

@ -11,14 +11,15 @@ Nim has one of the best FFI you can find - importing C/C++ is supported out of t
The goal of nimterop is to leverage the [tree-sitter](http://tree-sitter.github.io/tree-sitter/) engine to parse C/C++ code and then convert relevant portions of the AST into Nim definitions. [tree-sitter](https://github.com/tree-sitter) is a Github sponsored project that can parse a variety of languages into an AST which is then leveraged by the [Atom](https://atom.io/) editor for syntax highlighting and code folding. The advantages of this approach are multifold:
- Benefit from the tree-sitter community's investment into language parsing
- Wrap what is recognized in the AST rather than completely failing due to parsing errors
- Avoid depending on Nim compiler API which is evolving constantly and makes backwards compatibility a bit challenging
Most of the 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.
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 nimgen 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 feature set is still limited to C but is expanding rapidly. C++ support will be added once most popular C libraries can be wrapped seamlessly.
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.
Nimterop has seen some adoption within the community and the simplicity and success of this approach justifies additional investment of time and effort. Regardless, the goal is to make interop seamless so nimterop will focus on wrapping headers and not the outright conversion of C/C++ implementation.
Also, given tree-sitter can parse a variety of other languages, there might also be value in investigating how to wrap Rust and Go libraries.
__Installation__
Nimterop can be installed via [Nimble](https://github.com/nim-lang/nimble):
@ -37,8 +38,37 @@ This will download and install nimterop in the standard Nimble package location,
__Usage__
Detailed documentation can be found [here](https://nimterop.github.io/nimterop/theindex.html).
Check out the [wiki](https://github.com/nimterop/nimterop/wiki/Wrappers) for a list of all known wrappers that have been created using nimterop. Please do add your project once you are done so that others can benefit from your work.
Using the high-level `getHeader` API to perform all building and linking automatically:
```nim
import nimterop/[build, cimport]
static:
cDebug()
getHeader(
"header.h",
giturl = "https://github.com/username/repo",
dlurl = "https://website.org/download/repo-$1.tar.gz",
outdir = "build",
conFlags = "--disable-comp --enable-feature"
)
when not defined(headerStatic):
cImport(headerPath, recurse = true, dynlib = "headerLPath")
else:
cImport(headerPath, recurse = true)
```
This allows the user to control how the wrapper works - either pass `-d:headerStd` to search for `header.h` in the standard system path, `-d:headerGit` to clone the source from the specified git URL or `-d:headerDL` to get the source from download URL. Further, the `-d:headerSetVer=X.Y.Z` flag can be used to specify which version to use. It is used as the tag name for Git whereas for DL, it replaces `$1` in the URL defined.
The `-d:headerStatic` attempts to statically link the library. If it is omitted, the library is dynamically linked instead.
[lzma.nim](https://github.com/nimterop/nimterop/blob/master/tests/lzma.nim) is an example of a library using this high-level API.
The traditional approach is to manually compile in the code:
```nim
import nimterop/cimport
@ -52,10 +82,11 @@ cImport("clib.h")
cCompile("clib/src/*.c")
```
Check out [template.nim](https://github.com/nimterop/nimterop/blob/master/nimterop/template.nim) as a starting point for wrapping a new library. The template can be copied and trimmed down and modified as required. [templite.nim](https://github.com/nimterop/nimterop/blob/master/nimterop/templite.nim) is a shorter version for more experienced users.
Check out [template.nim](https://github.com/nimterop/nimterop/blob/master/nimterop/template.nim) as a starting point for wrapping a library using the traditional approach. The template can be copied and trimmed down and modified as required. [templite.nim](https://github.com/nimterop/nimterop/blob/master/nimterop/templite.nim) is a shorter version for more experienced users.
Refer to the ```tests``` directory for examples on how the library can be used.
The `toast` binary can also be used directly on the CLI:
```

View file

@ -1,8 +1,8 @@
version: '{build}'
image:
- Visual Studio 2015
- Ubuntu
- Visual Studio 2017
matrix:
fast_finish: true
@ -10,19 +10,19 @@ matrix:
environment:
matrix:
- NIM_VERSION: 0.19.6
- NIM_VERSION: 0.20.0
- NIM_VERSION: 0.20.2
- NIM_VERSION: 1.0.0
for:
-
matrix:
only:
- image: Visual Studio 2017
- image: Visual Studio 2015
environment:
ARCH: 32
MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/8.1.0/threads-posix/dwarf
MINGW_ARCHIVE: i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z
SFNET_URL: https://sourceforge.net/projects/msys2/files/REPOS/MINGW/i686
ARCH: 64
GIT_URL: https://github.com/git-for-windows/git/releases/download/v2.23.0.windows.1/
GIT_ARCHIVE: PortableGit-2.23.0-64-bit.7z.exe
install:
- CD c:\
@ -30,20 +30,23 @@ for:
echo %NIM_VERSION% &&
MKDIR binaries &&
CD binaries &&
appveyor DownloadFile "%MINGW_URL%/%MINGW_ARCHIVE%/download" -FileName "%MINGW_ARCHIVE%" &&
7z x -y "%MINGW_ARCHIVE%"> nul &&
del "%MINGW_ARCHIVE%" &&
MKDIR git &&
CD git &&
appveyor DownloadFile "%GIT_URL%/%GIT_ARCHIVE%" -FileName "%GIT_ARCHIVE%" &&
7z x -y -bd "%GIT_ARCHIVE%"> nul &&
del "%GIT_ARCHIVE%" &&
CD .. &&
appveyor DownloadFile "https://nim-lang.org/download/nim-%NIM_VERSION%_x%ARCH%.zip" -FileName "nim-%NIM_VERSION%_x%ARCH%.zip" &&
7z x -y "nim-%NIM_VERSION%_x%ARCH%.zip"> nul &&
del "nim-%NIM_VERSION%_x%ARCH%.zip")
- SET PATH=c:\binaries\mingw%ARCH%\bin;c:\binaries\nim-%NIM_VERSION%\bin;%USERPROFILE%\.nimble\bin;%PATH%
- SET PATH=c:\binaries\git\bin;C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;c:\binaries\nim-%NIM_VERSION%\bin;%USERPROFILE%\.nimble\bin;%PATH%
- CD %APPVEYOR_BUILD_FOLDER%
on_finish:
- 7z a -r buildlogs-win-pkgs.zip %USERPROFILE%\.nimble\pkgs
- appveyor PushArtifact buildlogs-win-pkgs.zip
- 7z a -r buildlogs-win-projects.zip c:\projects\*
- appveyor PushArtifact buildlogs-win-projects.zip
# on_finish:
# - 7z a -r buildlogs-win-pkgs.zip %USERPROFILE%\.nimble\pkgs
# - appveyor PushArtifact buildlogs-win-pkgs.zip
# - 7z a -r buildlogs-win-projects.zip c:\projects\*
# - appveyor PushArtifact buildlogs-win-projects.zip
cache:
- c:\binaries
@ -54,6 +57,8 @@ for:
- image: Ubuntu
install:
- sudo apt-get update
- sudo apt-get --yes --force-yes install liblzma-dev liblzma5 autopoint
- if [ ! -e /home/appveyor/binaries ]; then
echo $NIM_VERSION &&
mkdir /home/appveyor/binaries &&
@ -69,11 +74,11 @@ for:
- export PATH=/home/appveyor/binaries/nim-$NIM_VERSION/bin:~/.nimble/bin:$PATH
- cd $APPVEYOR_BUILD_FOLDER
on_finish:
- zip -r -q buildlogs-lin-pkgs.zip ~/.nimble/pkgs
- appveyor PushArtifact buildlogs-lin-pkgs.zip
- zip -r -q buildlogs-lin-projects.zip /home/appveyor/projects
- appveyor PushArtifact buildlogs-lin-projects.zip
# on_finish:
# - zip -r -q buildlogs-lin-pkgs.zip ~/.nimble/pkgs
# - appveyor PushArtifact buildlogs-lin-pkgs.zip
# - zip -r -q buildlogs-lin-projects.zip /home/appveyor/projects
# - appveyor PushArtifact buildlogs-lin-projects.zip
cache:
- /home/appveyor/binaries

View file

@ -1,6 +1,6 @@
# Package
version = "0.1.0"
version = "0.2.0"
author = "genotrance"
description = "C/C++ interop for Nim"
license = "MIT"
@ -14,7 +14,7 @@ installFiles = @["config.nims"]
# Dependencies
requires "nim >= 0.19.2", "regex >= 0.10.0", "cligen >= 0.9.17"
import strformat
import nimterop/docs
proc execCmd(cmd: string) =
echo "execCmd:" & cmd
@ -24,57 +24,27 @@ proc execTest(test: string) =
execCmd "nim c -r " & test
execCmd "nim cpp -r " & test
proc tsoloud() =
execTest "tests/tsoloud.nim"
task buildToast, "build toast":
# If need to manually rebuild (automatically built on 1st need)
execCmd(&"nim c -d:release nimterop/toast.nim")
execCmd("nim c -d:danger nimterop/toast.nim")
task docs, "Generate docs":
buildDocs(@["nimterop/all.nim"], "build/htmldocs")
task test, "Test":
buildToastTask()
proc testAll() =
execTest "tests/tnimterop_c.nim"
execCmd "nim cpp -r tests/tnimterop_cpp.nim"
execTest "tests/tpcre.nim"
## platform specific tests
# Platform specific tests
when defined(Windows):
execTest "tests/tmath.nim"
if defined(OSX) or defined(Windows) or not existsEnv("TRAVIS"):
tsoloud() # requires some libraries on linux, need them installed in TRAVIS
execTest "tests/tsoloud.nim"
const htmldocsDir = "build/htmldocs"
# getHeader tests
withDir("tests"):
execCmd("nim e getheader.nims")
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
import os
proc getNimRootDir(): string =
#[
hack, but works
alternatively (but more complex), use (from a nim file, not nims otherwise
you get Error: ambiguous call; both system.fileExists):
import "$nim/testament/lib/stdtest/specialpaths.nim"
nimRootDir
]#
fmt"{currentSourcePath}".parentDir.parentDir.parentDir
proc runNimDoc() =
execCmd &"nim doc -o:{htmldocsDir} --project --index:on nimterop/all.nim"
execCmd &"nim buildIndex -o:{htmldocsDir}/theindex.html {htmldocsDir}"
when declared(getNimRootDir):
#[
this enables doc search, works at least locally with:
cd {htmldocsDir} && python -m SimpleHTTPServer 9009
]#
execCmd &"nim js -o:{htmldocsDir}/dochack.js {getNimRootDir()}/tools/dochack/dochack.nim"
task test, "Test":
buildToastTask()
testAll()
runNimDoc()
task docs, "Generate docs":
runNimDoc()
task docsPublish, "Generate and publish docs":
# Uses: pip install ghp-import
runNimDoc()
execCmd &"ghp-import --no-jekyll -fp {htmldocsDir}"
docsTask()

View file

@ -4,4 +4,4 @@ Module that should import everything so that `nim doc --project nimtero/all` run
# TODO: make sure it does import everything.
import "."/[cimport, git, types, plugin, compat]
import "."/[cimport, build, types, plugin, compat]

844
nimterop/build.nim Normal file
View file

@ -0,0 +1,844 @@
import macros, osproc, regex, strformat, strutils, tables
import os except findExe, sleep
import "."/[compat]
proc sanitizePath*(path: string, noQuote = false, sep = $DirSep): string =
result = path.multiReplace([("\\\\", sep), ("\\", sep), ("/", sep)])
if not noQuote:
result = result.quoteShell
proc sleep*(milsecs: int) =
## Sleep at compile time
let
cmd =
when defined(windows):
"cmd /c timeout "
else:
"sleep "
(oup, ret) = gorgeEx(cmd & $(milsecs / 1000))
proc execAction*(cmd: string, retry = 0, nostderr = false): string =
## Execute an external command - supported at compile time
##
## Checks if command exits successfully before returning. If not, an
## error is raised.
var
ccmd = ""
ret = 0
when defined(Windows):
ccmd = "cmd /c " & cmd
elif defined(posix):
ccmd = cmd
else:
doAssert false
when nimvm:
(result, ret) = gorgeEx(ccmd)
else:
let opt = if nostderr: {poUsePath} else: {poStdErrToStdOut, poUsePath}
(result, ret) = execCmdEx(ccmd, opt)
if ret != 0:
if retry > 0:
sleep(500)
result = execAction(cmd, retry = retry - 1)
else:
doAssert true, "Command failed: " & $(ret, nostderr) & "\nccmd: " & ccmd & "\nresult:\n" & result
proc findExe*(exe: string): string =
## Find the specified executable using the `which`/`where` command - supported
## at compile time
var
cmd =
when defined(windows):
"where " & exe
else:
"which " & exe
(oup, code) = gorgeEx(cmd)
if code == 0:
return oup.splitLines()[0].strip()
proc mkDir*(dir: string) =
## Create a directory at compile time
##
## The `os` module is not available at compile time so a few
## crucial helper functions are included with nimterop.
if not dirExists(dir):
let
flag = when not defined(Windows): "-p" else: ""
discard execAction(&"mkdir {flag} {dir.sanitizePath}", retry = 2)
proc cpFile*(source, dest: string, move=false) =
## Copy a file from `source` to `dest` at compile time
let
source = source.replace("/", $DirSep)
dest = dest.replace("/", $DirSep)
cmd =
when defined(Windows):
if move:
"move /y"
else:
"copy /y"
else:
if move:
"mv -f"
else:
"cp -f"
discard execAction(&"{cmd} {source.sanitizePath} {dest.sanitizePath}", retry = 2)
proc mvFile*(source, dest: string) =
## Move a file from `source` to `dest` at compile time
cpFile(source, dest, move=true)
proc rmFile*(source: string, dir = false) =
## Remove a file or pattern at compile time
let
source = source.replace("/", $DirSep)
cmd =
when defined(Windows):
if dir:
"rd /s/q"
else:
"del /s/q/f"
else:
"rm -rf"
discard execAction(&"{cmd} {source.sanitizePath}", retry = 2)
proc rmDir*(source: string) =
## Remove a directory or pattern at compile time
rmFile(source, dir = true)
proc extractZip*(zipfile, outdir: string) =
## Extract a zip file using `powershell` on Windows and `unzip` on other
## systems to the specified output directory
var cmd = "unzip -o $#"
if defined(Windows):
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
"'System.IO.Compression.FileSystem'; " &
"[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\""
echo "# Extracting " & zipfile
discard execAction(&"cd {outdir.sanitizePath} && {cmd % zipfile}")
proc extractTar*(tarfile, outdir: string) =
## Extract a tar file using `tar`, `7z` or `7za` to the specified output directory
var
cmd = ""
name = ""
if findExe("tar").len != 0:
let
ext = tarfile.splitFile().ext.toLowerAscii()
typ =
case ext
of ".gz", ".tgz": "z"
of ".xz": "J"
of ".bz2": "j"
else: ""
cmd = "tar xvf" & typ & " " & tarfile.sanitizePath
else:
for i in ["7z", "7za"]:
if findExe(i).len != 0:
cmd = i & " x $#" % tarfile.sanitizePath
name = tarfile.splitFile().name
if ".tar" in name.toLowerAscii():
cmd &= " && " & i & " x $#" % name.sanitizePath
break
doAssert cmd.len != 0, "No extraction tool - tar, 7z, 7za - available for " & tarfile.sanitizePath
echo "# Extracting " & tarfile
discard execAction(&"cd {outdir.sanitizePath} && {cmd}")
if name.len != 0:
rmFile(outdir / name)
proc downloadUrl*(url, outdir: string) =
## Download a file using `curl` or `wget` (or `powershell` on Windows) to the specified directory
##
## If an archive file, it is automatically extracted after download.
let
file = url.extractFilename()
ext = file.splitFile().ext.toLowerAscii()
archives = @[".zip", ".xz", ".gz", ".bz2", ".tgz", ".tar"]
if not (ext in archives and fileExists(outdir/file)):
echo "# Downloading " & file
mkDir(outdir)
var cmd = findExe("curl")
if cmd.len != 0:
cmd &= " -Lk $# -o $#"
else:
cmd = findExe("wget")
if cmd.len != 0:
cmd &= " $# -o $#"
elif defined(Windows):
cmd = "powershell [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; wget $# -OutFile $#"
else:
doAssert false, "No download tool available - curl, wget"
discard execAction(cmd % [url, (outdir/file).sanitizePath])
if ext == ".zip":
extractZip(file, outdir)
elif ext in archives:
extractTar(file, outdir)
proc gitReset*(outdir: string) =
## Hard reset the git repository at the specified directory
echo "# Resetting " & outdir
let cmd = &"cd {outdir.sanitizePath} && git reset --hard"
while execAction(cmd).contains("Permission denied"):
sleep(1000)
echo "# Retrying ..."
proc gitCheckout*(file, outdir: string) =
## Checkout the specified `file` in the git repository at `outdir`
##
## This effectively resets all changes in the file and can be
## used to undo any changes that were made to source files to enable
## successful wrapping with `cImport()` or `c2nImport()`.
echo "# Resetting " & file
let file2 = file.relativePath outdir
let cmd = &"cd {outdir.sanitizePath} && git checkout {file2.sanitizePath}"
while execAction(cmd).contains("Permission denied"):
sleep(500)
echo "# Retrying ..."
proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
## Pull the specified git repository to the output directory
##
## `plist` is the list of specific files and directories or wildcards
## to sparsely checkout. Multiple values can be specified one entry per
## line. It is optional and if omitted, the entire repository will be
## checked out.
##
## `checkout` is the git tag, branch or commit hash to checkout once
## the repository is downloaded. This allows for pinning to a specific
## version of the code.
if dirExists(outdir/".git"):
gitReset(outdir)
return
let
outdirQ = outdir.sanitizePath
mkDir(outdir)
echo "# Setting up Git repo: " & url
discard execAction(&"cd {outdirQ} && git init .")
discard execAction(&"cd {outdirQ} && git remote add origin {url}")
if plist.len != 0:
# If a specific list of files is required, create a sparse checkout
# file for git in its config directory
let sparsefile = outdir / ".git/info/sparse-checkout"
discard execAction(&"cd {outdirQ} && git config core.sparsecheckout true")
writeFile(sparsefile, plist)
if checkout.len != 0:
echo "# Checking out " & checkout
discard execAction(&"cd {outdirQ} && git pull --tags origin master")
discard execAction(&"cd {outdirQ} && git checkout {checkout}")
else:
echo "# Pulling repository"
discard execAction(&"cd {outdirQ} && git pull --depth=1 origin master")
proc findFile*(file: string|Regex, dir: string, recurse = true, first = false): string =
## Find the file in the specified directory
##
## `file` can be a string or a regex object
##
## Turn off recursive search with `recurse` and stop on first match with
## `first`. Without it, the shortest match is returned.
when file is Regex:
var
rm: RegexMatch
else:
let
dir = dir / file.parentDir()
file = file.extractFilename
for f in walkDirRec(dir, yieldFilter = {pcFile, pcLinkToFile},
followFilter = if recurse: {pcDir} else: {}):
let
fn = f.extractFilename()
when file is string:
if (result.len == 0 or result.len > f.len) and fn == file:
result = f
if first: break
else:
if (result.len == 0 or result.len > f.len) and fn.match(file, rm):
result = f
if first: break
proc flagBuild*(base: string, flags: openArray[string]): string =
## Simple helper proc to generate flags for `configure`, `cmake`, etc.
##
## Every entry in `flags` is replaced into the `base` string and
## concatenated to the result.
##
## E.g.
## `base = "--disable-$#"`
## `flags = @["one", "two"]`
##
## `flagBuild(base, flags) => " --disable-one --disable-two"`
for i in flags:
result &= " " & base % i
proc configure*(path, check: string, flags = "") =
## Run the GNU `configure` command to generate all Makefiles or other
## build scripts in the specified path
##
## If a `configure` script is not present and an `autogen.sh` script
## is present, it will be run before attempting `configure`.
##
## Next, if `configure.ac` or `configure.in` exist, `autoreconf` will
## be executed.
##
## `check` is a file that will be generated by the `configure` command.
## This is required to prevent configure from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `configure` command.
if (path / check).fileExists():
return
echo "# Configuring " & path
if not fileExists(path / "configure"):
for i in @["autogen.sh", "build" / "autogen.sh"]:
if fileExists(path / i):
echo "# Running autogen.sh"
echo execAction(&"cd {(path / i).parentDir().sanitizePath} && bash autogen.sh")
break
if not fileExists(path / "configure"):
for i in @["configure.ac", "configure.in"]:
if fileExists(path / i):
echo "# Running autoreconf"
echo execAction(&"cd {path.sanitizePath} && autoreconf -fi")
break
if fileExists(path / "configure"):
echo "# Running configure " & flags
var
cmd = &"cd {path.sanitizePath} && bash configure"
if flags.len != 0:
cmd &= &" {flags}"
echo execAction(cmd)
doAssert (path / check).fileExists(), "# Configure failed"
proc getCmakePropertyStr(name, property, value: string): string =
&"\nset_target_properties({name} PROPERTIES {property} \"{value}\")\n"
proc getCmakeIncludePath*(paths: openArray[string]): string =
## Create a `cmake` flag to specify custom include paths
##
## Result can be included in the `flag` parameter for `cmake()` or
## the `cmakeFlags` parameter for `getHeader()`.
for path in paths:
result &= path & ";"
result = " -DCMAKE_INCLUDE_PATH=" & result[0 .. ^2].sanitizePath(sep = "/")
proc setCmakeProperty*(outdir, name, property, value: string) =
## Set a `cmake` property in `outdir / CMakeLists.txt` - usable in the `xxxPreBuild` hook
## for `getHeader()`
##
## `set_target_properties(name PROPERTIES property "value")`
let
cm = outdir / "CMakeLists.txt"
if cm.fileExists():
cm.writeFile(
cm.readFile() & getCmakePropertyStr(name, property, value)
)
proc setCmakeLibName*(outdir, name, prefix = "", oname = "", suffix = "") =
## Set a `cmake` property in `outdir / CMakeLists.txt` to specify a custom library output
## name - usable in the `xxxPreBuild` hook for `getHeader()`
##
## `prefix` is typically `lib`
## `oname` is the library name
## `suffix` is typically `.a`
##
## Sometimes, `cmake` generates non-standard library names - e.g. zlib compiles to
## `libzlibstatic.a` on Windows. This proc can help rename it to `libzlib.a` so that `getHeader()`
## can find it after the library is compiled.
##
## ```
## set_target_properties(name PROPERTIES PREFIX "prefix")
## set_target_properties(name PROPERTIES OUTPUT_NAME "oname")
## set_target_properties(name PROPERTIES SUFFIX "suffix")
## ```
let
cm = outdir / "CMakeLists.txt"
if cm.fileExists():
var
str = ""
if prefix.len != 0:
str &= getCmakePropertyStr(name, "PREFIX", prefix)
if oname.len != 0:
str &= getCmakePropertyStr(name, "OUTPUT_NAME", oname)
if suffix.len != 0:
str &= getCmakePropertyStr(name, "SUFFIX", suffix)
if str.len != 0:
cm.writeFile(cm.readFile() & str)
proc setCmakePositionIndependentCode*(outdir: string) =
## Set a `cmake` directive to create libraries with -fPIC enabled
let
cm = outdir / "CMakeLists.txt"
if cm.fileExists():
let
pic = "set(CMAKE_POSITION_INDEPENDENT_CODE ON)"
cmd = cm.readFile()
if not cmd.contains(pic):
cm.writeFile(
pic & "\n" & cmd
)
proc cmake*(path, check, flags: string) =
## Run the `cmake` command to generate all Makefiles or other
## build scripts in the specified path
##
## `path` will be created since typically `cmake` is run in an
## empty directory.
##
## `check` is a file that will be generated by the `cmake` command.
## This is required to prevent `cmake` from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `cmake` command.
## Unlike `configure`, it is required since typically it will be the
## path to the repository, typically `..` when `path` is a subdir.
if (path / check).fileExists():
return
echo "# Running cmake " & flags
echo "# Path: " & path
mkDir(path)
var
cmd = &"cd {path.sanitizePath} && cmake {flags}"
echo execAction(cmd)
doAssert (path / check).fileExists(), "# cmake failed"
proc make*(path, check: string|Regex, flags = "") =
## Run the `make` command to build all binaries in the specified path
##
## `check` is a file that will be generated by the `make` command.
## This is required to prevent `make` from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `make` command.
##
## If `make.exe` is missing and `mingw32-make.exe` is available, it will
## be copied over to make.exe in the same location.
if findFile(check, path).len != 0:
return
echo "# Running make " & flags
echo "# Path: " & path
var
cmd = findExe("make")
if cmd.len == 0:
cmd = findExe("mingw32-make")
if cmd.len != 0:
cpFile(cmd, cmd.replace("mingw32-make", "make"))
doAssert cmd.len != 0, "Make not found"
cmd = &"cd {path.sanitizePath} && make"
if flags.len != 0:
cmd &= &" {flags}"
echo execAction(cmd)
doAssert findFile(check, path).len != 0, "# make failed"
proc getGccPaths*(mode = "c"): seq[string] =
var
nul = when defined(Windows): "nul" else: "/dev/null"
mmode = if mode == "cpp": "c++" else: mode
inc = false
(outp, _) = gorgeEx(&"""{getEnv("CC", "gcc")} -Wp,-v -x{mmode} {nul}""")
for line in outp.splitLines():
if "#include <...> search starts here" in line:
inc = true
continue
elif "End of search list" in line:
break
if inc:
var
path = line.strip().myNormalizedPath()
if path notin result:
result.add path
when defined(osx):
result.add execAction("xcrun --show-sdk-path").strip() & "/usr/include"
proc getGccLibPaths*(mode = "c"): seq[string] =
var
nul = when defined(Windows): "nul" else: "/dev/null"
mmode = if mode == "cpp": "c++" else: mode
linker = when defined(OSX): "-Xlinker" else: ""
(outp, _) = gorgeEx(&"""{getEnv("CC", "gcc")} {linker} -v -x{mmode} {nul}""")
for line in outp.splitLines():
if "LIBRARY_PATH=" in line:
for path in line[13 .. ^1].split(PathSep):
var
path = path.strip().myNormalizedPath()
if path notin result:
result.add path
break
elif '\t' in line:
var
path = line.strip().myNormalizedPath()
if path notin result:
result.add path
when defined(osx):
result.add "/usr/lib"
proc getStdPath(header: string): string =
for inc in getGccPaths():
result = findFile(header, inc, recurse = false, first = true)
if result.len != 0:
break
proc getStdLibPath(lname: string): string =
for lib in getGccLibPaths():
result = findFile(re(lname), lib, recurse = false, first = true)
if result.len != 0:
break
proc getGitPath(header, url, outdir, version: string): string =
doAssert url.len != 0, "No git url setup for " & header
doAssert findExe("git").len != 0, "git executable missing"
gitPull(url, outdir, checkout = version)
result = findFile(header, outdir)
proc getDlPath(header, url, outdir, version: string): string =
doAssert url.len != 0, "No download url setup for " & header
var
dlurl = url
if "$#" in url or "$1" in url:
doAssert version.len != 0, "Need version for download url"
dlurl = url % version
else:
doAssert version.len == 0, "Download url does not contain version"
downloadUrl(dlurl, outdir)
var
dirname = ""
for kind, path in walkDir(outdir, relative = true):
if kind == pcFile and path != dlurl.extractFilename():
dirname = ""
break
elif kind == pcDir:
if dirname.len == 0:
dirname = path
else:
dirname = ""
break
if dirname.len != 0:
for kind, path in walkDir(outdir / dirname, relative = true):
mvFile(outdir / dirname / path, outdir / path)
result = findFile(header, outdir)
proc getLocalPath(header, outdir: string): string =
if outdir.len != 0:
result = findFile(header, outdir)
proc getNumProcs(): string =
when defined(windows):
getEnv("NUMBER_OF_PROCESSORS").strip()
elif defined(linux):
execAction("nproc").strip()
elif defined(macosx):
execAction("sysctl -n hw.ncpu").strip()
else:
"1"
proc buildLibrary(lname, outdir, conFlags, cmakeFlags, makeFlags: string): string =
var
conDeps = false
conDepStr = ""
cmakeDeps = false
cmakeDepStr = ""
lpath = findFile(re(lname), outdir)
makeFlagsProc = &"-j {getNumProcs()} {makeFlags}"
made = false
makePath = outdir
if lpath.len != 0:
return lpath
if not fileExists(outdir / "Makefile"):
if fileExists(outdir / "CMakeLists.txt"):
if findExe("cmake").len != 0:
var
gen = ""
when defined(windows):
if findExe("sh").len != 0:
let
uname = execAction("sh -c uname -a").toLowerAscii()
if uname.contains("msys"):
gen = "MSYS Makefiles".quoteShell
elif uname.contains("mingw"):
gen = "MinGW Makefiles".quoteShell & " -DCMAKE_SH=\"CMAKE_SH-NOTFOUND\""
else:
echo "Unsupported system: " & uname
else:
gen = "MinGW Makefiles".quoteShell
else:
gen = "Unix Makefiles".quoteShell
if findExe("ccache").len != 0:
gen &= " -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
makePath = outdir / "buildcache"
cmake(makePath, "Makefile", &".. -G {gen} {cmakeFlags}")
cmakeDeps = true
else:
cmakeDepStr &= "cmake executable missing"
if not cmakeDeps:
if findExe("bash").len != 0:
for file in @["configure", "configure.ac", "configure.in", "autogen.sh", "build/autogen.sh"]:
if fileExists(outdir / file):
configure(outdir, "Makefile", conFlags)
conDeps = true
break
else:
conDepStr &= "bash executable missing"
if fileExists(makePath / "Makefile"):
make(makePath, re(lname), makeFlagsProc)
made = true
var
error = ""
if not cmakeDeps and cmakeDepStr.len != 0:
error &= &"cmake capable but {cmakeDepStr}\n"
if not conDeps and conDepStr.len != 0:
error &= &"configure capable but {conDepStr}\n"
if error.len == 0:
error = "No build files found in " & outdir
doAssert cmakeDeps or conDeps or made, &"\n# Build configuration failed - {error}\n"
result = findFile(re(lname), outdir)
proc getDynlibExt(): string =
when defined(windows):
result = ".dll"
elif defined(linux):
result = ".so[0-9.]*"
elif defined(macosx):
result = ".dylib[0-9.]*"
var
gDefines {.compileTime.} = initTable[string, string]()
macro setDefines*(defs: static openArray[string]): untyped =
## Specify `-d:xxx` values in code instead of having to rely on the command
## line or `cfg` or `nims` files.
##
## At this time, Nim does not allow creation of `-d:xxx` defines in code. In
## addition, Nim only loads config files for the module being compiled but not
## for imported packages. This becomes a challenge when wanting to ship a wrapper
## library that wants to control `getHeader()` for an underlying package.
##
## E.g. nimarchive wanting to set `-d:lzmaStatic`
##
## The consumer of nimarchive would need to set such defines as part of their
## project, making it inconvenient.
##
## By calling this proc with the defines preferred before importing such a module,
## the caller can set the behavior in code instead.
##
## .. code-block:: nim
##
## setDefines(@["lzmaStatic", "lzmaDL", "lzmaSetVer=5.2.4"])
##
## import lzma
for def in defs:
let
nv = def.strip().split("=", maxsplit = 1)
if nv.len != 0:
let
n = nv[0]
v =
if nv.len == 2:
nv[1]
else:
""
gDefines[n] = v
macro clearDefines*(): untyped =
## Clear all defines set using `setDefines()`.
gDefines.clear()
macro getHeader*(header: static[string], giturl: static[string] = "", dlurl: static[string] = "", outdir: static[string] = "",
conFlags: static[string] = "", cmakeFlags: static[string] = "", makeFlags: static[string] = "",
altNames: static[string] = ""): untyped =
## Get the path to a header file for wrapping with
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ or
## `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_.
##
## This proc checks `-d:xxx` defines based on the header name (e.g. lzma from lzma.h),
## and accordingly employs different ways to obtain the source.
##
## `-d:xxxStd` - search standard system paths. E.g. `/usr/include` and `/usr/lib` on Linux
## `-d:xxxGit` - clone source from a git repo specified in `giturl`
## `-d:xxxDL` - download source from `dlurl` and extract if required
##
## This allows a single wrapper to be used in different ways depending on the user's needs.
## If no `-d:xxx` defines are specified, `outdir` will be searched for the header as is.
##
## `-d:xxxSetVer=x.y.z` can be used to specify which version to use. It is used as a tag
## name for Git whereas for DL, it replaces `$1` in the URL defined.
##
## All defines can also be set in code using `setDefines()`.
##
## The library is then configured (with `cmake` or `autotools` if possible) and built
## using `make`, unless using `-d:xxxStd` which presumes that the system package
## manager was used to install prebuilt headers and binaries.
##
## The header path is stored in `const xxxPath` and can be used in a `cImport()` call
## in the calling wrapper. The dynamic library path is stored in `const xxxLPath` and can
## be used for the `dynlib` parameter (within quotes) or with `{.passL.}`.
##
## `-d:xxxStatic` can be specified to statically link with the library instead. This
## will automatically add a `{.passL.}` call to the static library for convenience.
##
## `conFlags`, `cmakeFlags` and `makeFlags` allow sending custom parameters to `configure`,
## `cmake` and `make` in case additional configuration is required as part of the build process.
##
## `altNames` is a list of alternate names for the library - e.g. zlib uses `zlib.h` for the header but
## the typical lib name is `libz.so` and not `libzlib.so`. In this case, `altNames = "z"`. Comma
## separate for multiple alternate names.
##
## `xxxPreBuild` is a hook that is called after the source code is pulled from Git or downloaded but
## before the library is built. This might be needed if some initial prep needs to be done before
## compilation. A few values are provided to the hook to help provide context:
##
## `outdir` is the same `outdir` passed in and `header` is the discovered header path in the
## downloaded source code.
##
## Simply define `proc xxxPreBuild(outdir, header: string)` in the wrapper and it will get called
## prior to the build process.
var
name = header.extractFilename().split(".")[0]
stdStr = name & "Std"
gitStr = name & "Git"
dlStr = name & "DL"
staticStr = name & "Static"
verStr = name & "SetVer"
nameStd = newIdentNode(stdStr)
nameGit = newIdentNode(gitStr)
nameDL = newIdentNode(dlStr)
nameStatic = newIdentNode(staticStr)
path = newIdentNode(name & "Path")
lpath = newIdentNode(name & "LPath")
version = newIdentNode(verStr)
lname = newIdentNode(name & "LName")
preBuild = newIdentNode(name & "PreBuild")
lre = "(lib)?$1[_]?(static)?[0-9.\\-]*\\"
stdVal = gDefines.hasKey(stdStr)
gitVal = gDefines.hasKey(gitStr)
dlVal = gDefines.hasKey(dlStr)
staticVal = gDefines.hasKey(staticStr)
verVal =
if gDefines.hasKey(verStr):
gDefines[verStr]
else:
""
if altNames.len != 0:
let
names = "(" & name & "|" & altNames.replace(",", "|") & ")"
lre = lre % names
else:
lre = lre % name
result = newNimNode(nnkStmtList)
result.add(quote do:
const
`nameStd`* = when defined(`nameStd`): true else: `stdVal` == 1
`nameGit`* = when defined(`nameGit`): true else: `gitVal` == 1
`nameDL`* = when defined(`nameDL`): true else: `dlVal` == 1
`nameStatic`* = when defined(`nameStatic`): true else: `staticVal` == 1
`version`* {.strdefine.} = `verVal`
`lname` =
when `nameStatic`:
`lre` & ".a"
else:
`lre` & getDynlibExt()
when `nameStd`:
const
`path`* = getStdPath(`header`)
`lpath`* = getStdLibPath(`lname`)
else:
const
`path`* =
when `nameGit`:
getGitPath(`header`, `giturl`, `outdir`, `version`)
elif `nameDL`:
getDlPath(`header`, `dlurl`, `outdir`, `version`)
else:
getLocalPath(`header`, `outdir`)
when declared(`preBuild`):
static:
`preBuild`(`outdir`, `path`)
const
`lpath`* = buildLibrary(`lname`, `outdir`, `conFlags`, `cmakeFlags`, `makeFlags`)
static:
doAssert `path`.len != 0, "\nHeader " & `header` & " not found - " & "missing/empty outdir or -d:$1Std -d:$1Git or -d:$1DL not specified" % `name`
doAssert `lpath`.len != 0, "\nLibrary " & `lname` & " not found"
echo "# Including library " & `lpath`
when `nameStatic`:
{.passL: `lpath`.}
)

View file

@ -6,7 +6,7 @@ as a starting point for wrapping a new library. The template can be copied and
trimmed down and modified as required. `templite.nim <https://github.com/nimterop/nimterop/blob/master/nimterop/templite.nim>`_ is a shorter
version for more experienced users.
All ``{.compileTime.}`` procs must be used in a compile time context, e.g. using:
All `{.compileTime.}` procs must be used in a compile time context, e.g. using:
.. code-block:: c
@ -21,7 +21,7 @@ const CIMPORT {.used.} = 1
include "."/globals
import "."/[git, paths, types]
import "."/[build, paths, types]
export types
proc interpPath(dir: string): string=
@ -79,11 +79,11 @@ proc getFileDate(fullpath: string): string =
ret = 0
cmd =
when defined(Windows):
&"cmd /c for %a in ({fullpath.quoteShell}) do echo %~ta"
&"cmd /c for %a in ({fullpath.sanitizePath}) do echo %~ta"
elif defined(Linux):
&"stat -c %y {fullpath.quoteShell}"
&"stat -c %y {fullpath.sanitizePath}"
elif defined(OSX):
&"stat -f %m {fullpath.quoteShell}"
&"stat -f %m {fullpath.sanitizePath}"
(result, ret) = gorgeEx(cmd)
@ -120,7 +120,7 @@ proc getNimCheckError(output: string): tuple[tmpFile, errors: string] =
getCurrentCompilerExe()
else:
"nim"
(check, _) = gorgeEx(&"{nim} check {result.tmpFile.quoteShell}")
(check, _) = gorgeEx(&"{nim} check {result.tmpFile.sanitizePath}")
result.errors = "\n\n" & check
@ -131,7 +131,7 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = "",
cmd = when defined(Windows): "cmd /c " else: ""
let toastExe = toastExePath()
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.quoteShell &
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.sanitizePath &
" make sure 'nimble build' or 'nimble install' built it"
cmd &= &"{toastExe} --preprocess"
@ -145,7 +145,7 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = "",
cmd.add &" --defines+={i.quoteShell}"
for i in gStateCT.includeDirs:
cmd.add &" --includeDirs+={i.quoteShell}"
cmd.add &" --includeDirs+={i.sanitizePath}"
if not noNimout:
cmd.add &" --pnim"
@ -157,25 +157,17 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = "",
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
cmd.add &" --nim:{getCurrentCompilerExe().sanitizePath}"
if gStateCT.pluginSourcePath.nBl:
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.sanitizePath}"
cmd.add &" {fullpath.quoteShell}"
cmd.add &" {fullpath.sanitizePath}"
# see https://github.com/nimterop/nimterop/issues/69
(result, ret) = gorgeEx(cmd, cache=getCacheValue(fullpath))
doAssert ret == 0, getToastError(result)
proc getGccPaths(mode = "c"): string =
var
ret = 0
nul = when defined(Windows): "nul" else: "/dev/null"
mmode = if mode == "cpp": "c++" else: mode
(result, ret) = gorgeEx(&"""{getEnv("CC", "gcc")} -Wp,-v -x{mmode} {nul}""")
macro cOverride*(body): untyped =
## When the wrapper code generated by nimterop is missing certain symbols or not
## accurate, it may be required to hand wrap them. Define them in a
@ -202,7 +194,7 @@ macro cOverride*(body): untyped =
## proc svGetCallerInfo(fileName: var cstring; lineNumber: var cint)
##
## Using the `cOverride() <cimport.html#cOverride.m>`_ block, nimterop
## can be instructed to skip over ``svGetCallerInfo()``. This works for procs,
## can be instructed to skip over `svGetCallerInfo()`. This works for procs,
## consts and types.
##
## `cOverride() <cimport.html#cOverride.m>`_ only affects calls to
@ -236,7 +228,8 @@ proc cSkipSymbol*(skips: seq[string]) {.compileTime.} =
gStateCT.symOverride.add skips
macro cPlugin*(body): untyped =
## When `cOverride() <cimport.html#cOverride.m>`_ and `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## When `cOverride() <cimport.html#cOverride.m>`_ and
## `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## are not adequate, the `cPlugin() <cimport.html#cPlugin.m>`_ macro can be used
## to customize the generated Nim output. The following callbacks are available at
## this time.
@ -261,7 +254,7 @@ macro cPlugin*(body): untyped =
## - `nskEnumField` for enum (field) names, though they are in the global namespace as `nskConst`
## - `nskProc` - for proc names
##
## ``nimterop/plugins`` is implicitly imported to provide access to standard plugin facilities.
## `nimterop/plugins` is implicitly imported to provide access to standard plugin facilities.
##
## `cPlugin() <cimport.html#cPlugin.m>`_ only affects calls to
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ that follow it.
@ -295,7 +288,7 @@ macro cPlugin*(body): untyped =
gStateCT.pluginSourcePath = path
proc cSearchPath*(path: string): string {.compileTime.}=
## Get full path to file or directory ``path`` in search path configured
## Get full path to file or directory `path` in search path configured
## using `cAddSearchDir() <cimport.html#cAddSearchDir%2Cstring>`_ and
## `cAddStdDir() <cimport.html#cAddStdDir,string>`_.
##
@ -322,20 +315,21 @@ proc cDebug*() {.compileTime.} =
proc cDisableCaching*() {.compileTime.} =
## Disable caching of generated Nim code - useful during wrapper development
##
## If files included by header being processed by `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## If files included by header being processed by
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## change and affect the generated content, they will be ignored and the cached
## value will continue to be used . Use `cDisableCaching() <cimport.html#cDisableCaching>`_
## to avoid this scenario during development.
##
## ``nim -f`` was broken prior to 0.19.4 but can also be used to flush the cached content.
## `nim -f` was broken prior to 0.19.4 but can also be used to flush the cached content.
gStateCT.nocache = true
macro cDefine*(name: static string, val: static string = ""): untyped =
## ``#define`` an identifer that is forwarded to the C/C++ preprocessor if
## `#define` an identifer that is forwarded to the C/C++ preprocessor if
## called within `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_ as well as to the
## C/C++ compiler during Nim compilation using ``{.passC: "-DXXX".}``
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## as well as to the C/C++ compiler during Nim compilation using `{.passC: "-DXXX".}`
result = newNimNode(nnkStmtList)
@ -356,7 +350,7 @@ macro cDefine*(name: static string, val: static string = ""): untyped =
echo result.repr & "\n"
proc cAddSearchDir*(dir: string) {.compileTime.} =
## Add directory ``dir`` to the search path used in calls to
## Add directory `dir` to the search path used in calls to
## `cSearchPath() <cimport.html#cSearchPath,string>`_.
runnableExamples:
import paths, os
@ -370,8 +364,8 @@ proc cAddSearchDir*(dir: string) {.compileTime.} =
macro cIncludeDir*(dir: static string): untyped =
## Add an include directory that is forwarded to the C/C++ preprocessor if
## called within `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_ as well as to the
## C/C++ compiler during Nim compilation using ``{.passC: "-IXXX".}``.
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## as well as to the C/C++ compiler during Nim compilation using `{.passC: "-IXXX".}`.
var dir = interpPath(dir)
result = newNimNode(nnkStmtList)
@ -386,42 +380,34 @@ macro cIncludeDir*(dir: static string): untyped =
echo result.repr
proc cAddStdDir*(mode = "c") {.compileTime.} =
## Add the standard ``c`` [default] or ``cpp`` include paths to search
## Add the standard `c` [default] or `cpp` include paths to search
## path used in calls to `cSearchPath() <cimport.html#cSearchPath,string>`_
runnableExamples:
static: cAddStdDir()
import os
doAssert cSearchPath("math.h").existsFile
var
inc = false
for line in getGccPaths(mode).splitLines():
if "#include <...> search starts here" in line:
inc = true
continue
elif "End of search list" in line:
break
if inc:
cAddSearchDir line.strip()
for inc in getGccPaths(mode):
cAddSearchDir inc
macro cCompile*(path: static string, mode = "c", exclude = ""): untyped =
## Compile and link C/C++ implementation into resulting binary using ``{.compile.}``
## Compile and link C/C++ implementation into resulting binary using `{.compile.}`
##
## ``path`` can be a specific file or contain wildcards:
## `path` can be a specific file or contain wildcards:
##
## .. code-block:: nim
##
## cCompile("file.c")
## cCompile("path/to/*.c")
##
## ``mode`` recursively searches for code files in ``path``.
## `mode` recursively searches for code files in `path`.
##
## ``c`` searches for ``*.c`` whereas ``cpp`` searches for ``*.C *.cpp *.c++ *.cc *.cxx``
## `c` searches for `*.c` whereas `cpp` searches for `*.C *.cpp *.c++ *.cc *.cxx`
##
## .. code-block:: nim
##
## cCompile("path/to/dir", "cpp")
##
## ``exclude`` can be used to exclude files by partial string match. Comma separated to
## `exclude` can be used to exclude files by partial string match. Comma separated to
## specify multiple exclude strings
##
## .. code-block:: nim
@ -499,16 +485,16 @@ macro cCompile*(path: static string, mode = "c", exclude = ""): untyped =
macro cImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
## Import all supported definitions from specified header file. Generated
## content is cached in ``nimcache`` until ``filename`` changes unless
## `cDisableCaching() <cimport.html#cDisableCaching>`_ is set. ``nim -f``
## content is cached in `nimcache` until `filename` changes unless
## `cDisableCaching() <cimport.html#cDisableCaching>`_ is set. `nim -f`
## can also be used after Nim v0.19.4 to flush the cache.
##
## ``recurse`` can be used to generate Nim wrappers from ``#include`` files
## referenced in ``filename``. This is only done for files in the same
## directory as ``filename`` or in a directory added using
## `recurse` can be used to generate Nim wrappers from `#include` files
## referenced in `filename`. This is only done for files in the same
## directory as `filename` or in a directory added using
## `cIncludeDir() <cimport.html#cIncludeDir.m>`_
##
## ``dynlib`` can be used to specify the Nim string to use to specify the dynamic
## `dynlib` can be used to specify the Nim string to use to specify the dynamic
## library to load the imported symbols from. For example:
##
## .. code-block:: nim
@ -527,21 +513,21 @@ macro cImport*(filename: static string, recurse: static bool = false, dynlib: st
##
## cImport("pcre.h", dynlib="dynpcre")
##
## If ``dynlib`` is not specified, the C/C++ implementation files can be compiled in
## with `cCompile() <cimport.html#cCompile.m%2C%2Cstring%2Cstring>`_, or the ``{.passL.}`` pragma
## can be used to specify the static lib to link.
## If `dynlib` is not specified, the C/C++ implementation files can be compiled in
## with `cCompile() <cimport.html#cCompile.m%2C%2Cstring%2Cstring>`_, or the
## `{.passL.}` pragma can be used to specify the static lib to link.
##
## ``mode`` is purely for forward compatibility when toast adds C++ support. It can
## `mode` is purely for forward compatibility when toast adds C++ support. It can
## be ignored for the foreseeable future.
##
## ``flags`` can be used to pass any other command line arguments to ``toast``.
## `flags` can be used to pass any other command line arguments to `toast`.
result = newNimNode(nnkStmtList)
let
fullpath = findPath(filename)
echo "# Importing " & fullpath
echo "# Importing " & fullpath.sanitizePath
let
output = getToast(fullpath, recurse, dynlib, mode, flags)
@ -560,24 +546,24 @@ macro cImport*(filename: static string, recurse: static bool = false, dynlib: st
macro c2nImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
## Import all supported definitions from specified header file using ``c2nim``
## Import all supported definitions from specified header file using `c2nim`
##
## Similar to `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ but uses ``c2nim`` to generate
## the Nim wrapper instead of ``toast``. Note that neither
## Similar to `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## but uses `c2nim` to generate the Nim wrapper instead of `toast`. Note that neither
## `cOverride() <cimport.html#cOverride.m>`_, `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## nor `cPlugin() <cimport.html#cPlugin.m>`_ have any impact on ``c2nim``.
## nor `cPlugin() <cimport.html#cPlugin.m>`_ have any impact on `c2nim`.
##
## ``toast`` is only used to preprocess the header file and recurse
## `toast` is only used to preprocess the header file and recurse
## if specified.
##
## ``mode`` should be set to ``cpp`` for c2nim to wrap C++ headers.
## `mode` should be set to `cpp` for c2nim to wrap C++ headers.
##
## ``flags`` can be used to pass other command line arguments to ``c2nim``.
## `flags` can be used to pass other command line arguments to `c2nim`.
##
## ``nimterop`` does not depend on ``c2nim`` as a ``nimble`` dependency so it
## `nimterop` does not depend on `c2nim` as a `nimble` dependency so it
## does not get installed automatically. Any wrapper or library that requires this proc
## needs to install ``c2nim`` with ``nimble install c2nim`` or add it as a dependency in
## its own ``.nimble`` file.
## needs to install `c2nim` with `nimble install c2nim` or add it as a dependency in
## its own `.nimble` file.
result = newNimNode(nnkStmtList)

View file

@ -6,10 +6,18 @@ put everything that requires `when (NimMajor, NimMinor, NimPatch)` here
import os
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
proc myNormalizedPath*(path: string): string = path.normalizedPath()
export relativePath
else:
import std/[ospaths,strutils]
proc myNormalizedPath*(path: string): string =
result = path.normalizedPath()
when defined(windows):
result = result.strip(trailing = false, chars = {'\\'})
proc relativePath*(file, base: string): string =
## naive version of `os.relativePath` ; remove after nim >= 0.19.9
runnableExamples:
@ -17,8 +25,8 @@ else:
check:
"/foo/bar/baz/log.txt".unixToNativePath.relativePath("/foo/bar".unixToNativePath) == "baz/log.txt".unixToNativePath
"foo/bar/baz/log.txt".unixToNativePath.relativePath("foo/bar".unixToNativePath) == "baz/log.txt".unixToNativePath
var base = base.normalizedPath
var file = file.normalizedPath
var base = base.myNormalizedPath
var file = file.myNormalizedPath
if not base.endsWith DirSep: base.add DirSep
doAssert file.startsWith base
result = file[base.len .. ^1]

47
nimterop/docs.nim Normal file
View file

@ -0,0 +1,47 @@
import macros, strformat
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
from os import parentDir
proc getNimRootDir(): string =
#[
hack, but works
alternatively (but more complex), use (from a nim file, not nims otherwise
you get Error: ambiguous call; both system.fileExists):
import "$nim/testament/lib/stdtest/specialpaths.nim"
nimRootDir
]#
fmt"{currentSourcePath}".parentDir.parentDir.parentDir
proc buildDocs*(files: seq[string], path: string, baseDir = getProjectPath() & "/") =
## Generate docs for all specified nim `files` to the specified `path`
##
## `baseDir` is the project path by default and `files` and `path` are relative
## to that directory. Set to "" if using absolute paths.
##
## Use the `--publish` flag with nimble to publish docs contained in
## `path` to Github in the `gh-pages` branch. This requires the ghp-import
## package for Python: `pip install ghp-import`
##
## WARNING: `--publish` will destroy any existing content in this branch.
let
baseDir =
if baseDir == "/":
getCurrentDir() & "/"
else:
baseDir
path = baseDir & path
for file in files:
echo gorge(&"nim doc -o:{path} --project --index:on {baseDir & file}")
echo gorge(&"nim buildIndex -o:{path}/theindex.html {path}")
when declared(getNimRootDir):
#[
this enables doc search, works at least locally with:
cd {path} && python -m SimpleHTTPServer 9009
]#
echo gorge(&"nim js -o:{path}/dochack.js {getNimRootDir()}/tools/dochack/dochack.nim")
for i in 0 .. paramCount():
if paramStr(i) == "--publish":
echo gorge(&"ghp-import --no-jekyll -fp {path}")
break

View file

@ -2,7 +2,7 @@ import dynlib, macros, os, sequtils, sets, strformat, strutils, tables, times
import regex
import "."/[git, globals, plugin, treesitter/api]
import "."/[build, compat, globals, plugin, treesitter/api]
const gReserved = """
addr and as asm
@ -26,7 +26,7 @@ when while
xor
yield""".split(Whitespace).toSet()
const gTypeMap = {
const gTypeMap* = {
# char
"char": "cchar",
"signed char": "cschar",
@ -39,6 +39,8 @@ const gTypeMap = {
"signed short int": "cshort",
"unsigned short": "cushort",
"unsigned short int": "cushort",
"uShort": "cushort",
"u_short": "cushort",
# int
"int": "cint",
@ -47,6 +49,8 @@ const gTypeMap = {
"ssize_t": "cint",
"unsigned": "cuint",
"unsigned int": "cuint",
"uInt": "cuint",
"u_int": "cuint",
"size_t": "cuint",
# long
@ -57,6 +61,8 @@ const gTypeMap = {
"off_t": "clong",
"unsigned long": "culong",
"unsigned long int": "culong",
"uLong": "culong",
"u_long": "culong",
# long long
"long long": "clonglong",
@ -73,17 +79,14 @@ const gTypeMap = {
"long double": "clongdouble"
}.toTable()
proc sanitizePath*(path: string): string =
path.multiReplace([("\\\\", $DirSep), ("\\", $DirSep), ("/", $DirSep)])
proc getType*(str: string): string =
if str == "void":
return "object"
result = str.strip(chars={'_'}).
replace(re"\s+", " ").
replace(re"([u]?int[\d]+)_t", "$1").
replace(re"([u]?int)ptr_t", "ptr $1")
replace(re"^([u]?int[\d]+)_t$", "$1").
replace(re"^([u]?int)ptr_t$", "ptr $1")
if gTypeMap.hasKey(result):
result = gTypeMap[result]
@ -205,15 +208,15 @@ proc getPreprocessor*(gState: State, fullpath: string, mode = "cpp"): string =
rdata: seq[string] = @[]
start = false
sfile = fullpath.sanitizePath
sfile = fullpath.sanitizePath(noQuote = true)
for inc in gState.includeDirs:
cmd &= &"-I{inc.quoteShell} "
cmd &= &"-I{inc.sanitizePath} "
for def in gState.defines:
cmd &= &"-D{def} "
cmd &= &"{fullpath.quoteShell}"
cmd &= &"{fullpath.sanitizePath}"
# Include content only from file
for line in execAction(cmd).splitLines():
@ -221,19 +224,19 @@ proc getPreprocessor*(gState: State, fullpath: string, mode = "cpp"): string =
if line.len > 1 and line[0 .. 1] == "# ":
start = false
let
saniLine = line.sanitizePath
saniLine = line.sanitizePath(noQuote = true)
if sfile in saniLine:
start = true
elif not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
start = true
elif gState.recurse:
let
pDir = sfile.expandFilename().parentDir().sanitizePath()
pDir = sfile.expandFilename().parentDir().sanitizePath(noQuote = true)
if pDir.len == 0 or pDir in saniLine:
start = true
else:
for inc in gState.includeDirs:
if inc.absolutePath().sanitizePath in saniLine:
if inc.absolutePath().sanitizePath(noQuote = true) in saniLine:
start = true
break
else:
@ -395,7 +398,7 @@ proc loadPlugin*(gState: State, sourcePath: string) =
pdll = sourcePath.dll
if not fileExists(pdll) or
sourcePath.getLastModificationTime() > pdll.getLastModificationTime():
discard execAction(&"{gState.nim.quoteShell} c --app:lib {sourcePath.quoteShell}")
discard execAction(&"{gState.nim.sanitizePath} c --app:lib {sourcePath.sanitizePath}")
doAssert fileExists(pdll), "No plugin binary generated for " & sourcePath
let lib = loadLib(pdll)
@ -403,3 +406,9 @@ proc loadPlugin*(gState: State, sourcePath: string) =
gState.onSymbol = cast[OnSymbol](lib.symAddr("onSymbol"))
doAssert gState.onSymbol != nil, "onSymbol() load failed from " & pdll
proc expandSymlinkAbs*(path: string): string =
try:
result = path.expandSymlink().absolutePath(path.parentDir()).myNormalizedPath()
except:
result = path

View file

@ -1,294 +1 @@
import os, osproc, strformat, strutils
import "."/[compat]
proc execAction*(cmd: string, nostderr=false): string =
## Execute an external command - supported at compile time
##
## Checks if command exits successfully before returning. If not, an
## error is raised.
var
ccmd = ""
ret = 0
when defined(Windows):
ccmd = "cmd /c " & cmd
elif defined(posix):
ccmd = cmd
else:
doAssert false
when nimvm:
(result, ret) = gorgeEx(ccmd)
else:
let opt = if nostderr: {poUsePath} else: {poStdErrToStdOut, poUsePath}
(result, ret) = execCmdEx(ccmd, opt)
doAssert ret == 0, "Command failed: " & $(ret, nostderr) & "\nccmd: " & ccmd & "\nresult:\n" & result
proc findExe*(exe: string): string =
## Find the specified executable using the which/where command - supported
## at compile time
var
cmd =
when defined(windows):
"where " & exe
else:
"which " & exe
(oup, code) = gorgeEx(cmd)
if code == 0:
return oup.strip()
proc mkDir*(dir: string) =
## Create a directory at cmopile time
##
## The `os` module is not available at compile time so a few
## crucial helper functions are included with nimterop.
if not dirExists(dir):
let
flag = when not defined(Windows): "-p" else: ""
discard execAction(&"mkdir {flag} {dir.quoteShell}")
proc cpFile*(source, dest: string, move=false) =
## Copy a file from source to destination at compile time
let
source = source.replace("/", $DirSep)
dest = dest.replace("/", $DirSep)
cmd =
when defined(Windows):
if move:
"move /y"
else:
"copy /y"
else:
if move:
"mv -f"
else:
"cp -f"
discard execAction(&"{cmd} {source.quoteShell} {dest.quoteShell}")
proc mvFile*(source, dest: string) =
## Move a file from source to destination at compile time
cpFile(source, dest, move=true)
proc rmFile*(source: string, dir = false) =
## Remove a file or pattern at compile time
let
source = source.replace("/", $DirSep)
cmd =
when defined(Windows):
if dir:
"rd /s/q"
else:
"del /s/q/f"
else:
"rm -rf"
discard execAction(&"{cmd} {source.quoteShell}")
proc rmDir*(source: string) =
## Remove a directory or pattern at compile time
rmFile(source, dir = true)
proc extractZip*(zipfile, outdir: string) =
## Extract a zip file using powershell on Windows and unzip on other
## systems to the specified output directory
var cmd = "unzip -o $#"
if defined(Windows):
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
"'System.IO.Compression.FileSystem'; " &
"[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\""
echo "# Extracting " & zipfile
discard execAction(&"cd {outdir.quoteShell} && {cmd % zipfile}")
proc downloadUrl*(url, outdir: string) =
## Download a file using curl or wget (or powershell on Windows) to the specified directory
##
## If a zip file, it is automatically extracted after download.
let
file = url.extractFilename()
ext = file.splitFile().ext.toLowerAscii()
if not (ext == ".zip" and fileExists(outdir/file)):
echo "# Downloading " & file
mkDir(outdir)
var cmd = findExe("curl")
if cmd.len != 0:
cmd &= " -L $# -o $#"
else:
cmd = findExe("wget")
if cmd.len != 0:
cmd &= " $# -o $#"
elif defined(Windows):
cmd = "powershell [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; wget $# -OutFile $#"
else:
doAssert false, "No download tool available - curl, wget"
discard execAction(cmd % [url, (outdir/file).quoteShell])
if ext == ".zip":
extractZip(file, outdir)
proc gitReset*(outdir: string) =
## Hard reset the git repository at the specified directory
echo "# Resetting " & outdir
let cmd = &"cd {outdir.quoteShell} && git reset --hard"
while execAction(cmd).contains("Permission denied"):
sleep(1000)
echo "# Retrying ..."
proc gitCheckout*(file, outdir: string) =
## Checkout the specified file in the git repository specified
##
## This effectively resets all changes in the file and can be
## used to undo any changes that were made to source files to enable
## successful wrapping with `cImport()` or `c2nImport()`.
echo "# Resetting " & file
let file2 = file.relativePath outdir
let cmd = &"cd {outdir.quoteShell} && git checkout {file2.quoteShell}"
while execAction(cmd).contains("Permission denied"):
sleep(500)
echo "# Retrying ..."
proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
## Pull the specified git repository to the output directory
##
## `plist` is the list of specific files and directories or wildcards
## to sparsely checkout. Multiple values can be specified one entry per
## line. It is optional and if omitted, the entire repository will be
## checked out.
##
## `checkout` is the git tag, branch or commit hash to checkout once
## the repository is downloaded. This allows for pinning to a specific
## version of the code.
if dirExists(outdir/".git"):
gitReset(outdir)
return
let
outdirQ = outdir.quoteShell
mkDir(outdir)
echo "# Setting up Git repo: " & url
discard execAction(&"cd {outdirQ} && git init .")
discard execAction(&"cd {outdirQ} && git remote add origin {url}")
if plist.len != 0:
# If a specific list of files is required, create a sparse checkout
# file for git in its config directory
let sparsefile = outdir / ".git/info/sparse-checkout"
discard execAction(&"cd {outdirQ} && git config core.sparsecheckout true")
writeFile(sparsefile, plist)
if checkout.len != 0:
echo "# Checking out " & checkout
discard execAction(&"cd {outdirQ} && git pull --tags origin master")
discard execAction(&"cd {outdirQ} && git checkout {checkout}")
else:
echo "# Pulling repository"
discard execAction(&"cd {outdirQ} && git pull --depth=1 origin master")
proc configure*(path, check: string, flags = "") =
## Run the GNU `configure` command to generate all Makefiles or other
## build scripts in the specified path
##
## If a `configure` script is not present and an `autogen.sh` script
## is present, it will be run before attempting `configure`.
##
## `check` is a file that will be generated by the `configure` command.
## This is required to prevent configure from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `configure` command.
if (path / check).fileExists():
return
echo "# Configuring " & path
if not fileExists(path / "configure"):
for i in @[path / "autogen.sh", path / "build" / "autogen.sh"]:
if fileExists(i):
echo "# Running autogen.sh"
discard execAction(&"cd {i.parentDir().quoteShell} && bash autogen.sh")
break
if fileExists(path / "configure"):
echo "# Running configure " & flags
var
cmd = &"cd {path.quoteShell} && bash configure"
if flags.len != 0:
cmd &= &" {flags}"
echo execAction(cmd)
doAssert (path / check).fileExists(), "# Configure failed"
proc cmake*(path, check, flags: string) =
## Run the `cmake` command to generate all Makefiles or other
## build scripts in the specified path
##
## `path` will be created since typically `cmake` is run in an
## empty directory.
##
## `check` is a file that will be generated by the `cmake` command.
## This is required to prevent `cmake` from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `cmake` command.
## Unlike `configure`, it is required since typically it will be the
## path to the repository, typically `..` when `path` is a subdir.
if (path / check).fileExists():
return
echo "# Running cmake " & flags
echo "# Path: " & path
mkDir(path)
var
cmd = &"cd {path.quoteShell} && cmake {flags}"
echo execAction(cmd)
doAssert (path / check).fileExists(), "# cmake failed"
proc make*(path, check: string, flags = "") =
## Run the `make` command to build all binaries in the specified path
##
## `check` is a file that will be generated by the `make` command.
## This is required to prevent `make` from running on every build. It
## is relative to the `path` and should not be an absolute path.
##
## `flags` are any flags that should be passed to the `make` command.
##
## If make.exe is missing and mingw32-make.exe is available, it will
## be copied over to make.exe in the same location.
if (path / check).fileExists():
return
echo "# Running make " & flags
echo "# Path: " & path
var
cmd = findExe("make")
if cmd.len == 0:
cmd = findExe("mingw32-make")
if cmd.len != 0:
cpFile(cmd, cmd.replace("mingw32-make", "make"))
doAssert cmd.len != 0, "Make not found"
cmd = &"cd {path.quoteShell} && make"
if flags.len != 0:
cmd &= &" {flags}"
echo execAction(cmd)
doAssert (path / check).fileExists(), "# make failed"
include build

View file

@ -73,6 +73,7 @@ proc initGrammar(): Grammar =
(type_identifier)
)
{paramListGrammar}
(noexcept|throw_specifier?)
)
"""
@ -90,7 +91,7 @@ proc initGrammar(): Grammar =
"""
template funcParamCommon(fname, pname, ptyp, pptr, pout, count, i: untyped): untyped =
ptyp = nimState.getIdentifier(nimState.data[i].val, nskType, fname)
ptyp = nimState.getIdentifier(nimState.data[i].val, nskType, fname).getType()
pptr = ""
while i+1 < nimState.data.len and nimState.data[i+1].name == "pointer_declarator":
@ -136,7 +137,7 @@ proc initGrammar(): Grammar =
var
i = 0
typ = nimState.getIdentifier(nimState.data[i].val, nskType)
typ = nimState.getIdentifier(nimState.data[i].val, nskType).getType()
name = ""
nname = ""
tptr = ""
@ -160,7 +161,7 @@ proc initGrammar(): Grammar =
let
pragma = nimState.getPragma(nimState.getImportC(name, nname))
if typ.nBl and nname.nBl and nimState.addNewIdentifer(nname):
if nname notin gTypeMap and typ.nBl and nname.nBl and nimState.addNewIdentifer(nname):
if i < nimState.data.len and nimState.data[^1].name == "function_declarator":
var
fname = nname
@ -574,7 +575,7 @@ proc initGrammar(): Grammar =
if fnname.nBl and nimState.addNewIdentifer(fnname):
let
ftyp = nimState.getIdentifier(nimState.data[0].val, nskType, fnname)
ftyp = nimState.getIdentifier(nimState.data[0].val, nskType, fnname).getType()
pragma = nimState.getPragma(nimState.getImportC(fname, fnname), "cdecl")
if fptr.len != 0 or ftyp != "object":

View file

@ -1,6 +1,6 @@
import os, strutils
import "."/[git, paths]
import "."/[build, paths]
proc treesitterSetup*() =
gitPull("https://github.com/tree-sitter/tree-sitter", incDir() / "treesitter", """

View file

@ -1,6 +1,6 @@
import os, strutils
import nimterop/[cimport, git, paths]
import nimterop/[cimport, build, paths]
# Documentation:
# https://github.com/nimterop/nimterop
@ -33,7 +33,10 @@ src/*.c
# Run GNU configure on the source
when defined(posix):
configure(srcDir, fileThatShouldGetGenerated)
configure(srcDir, fileThatShouldGetGenerated, flagsToConfigure)
# Run cmake on the source
cmake(srcDir/"build", fileThatShouldGetGenerated, flagsToCmake)
# Run standard file/directory operations with mkDir(), cpFile(), mvFile()

View file

@ -1,6 +1,6 @@
import os, strutils
import nimterop/[cimport, git, paths]
import nimterop/[cimport, build, paths]
const
baseDir = currentSourcePath.parentDir()/"build"

View file

@ -145,7 +145,7 @@ proc main(
if gState.pnim:
printNimHeader()
for src in source:
gState.process(src, astTable)
gState.process(src.expandSymlinkAbs(), astTable)
when isMainModule:
import cligen

View file

@ -7,17 +7,17 @@ import ".."/[setup, paths, types]
static:
treesitterSetup()
const sourcePath = incDir() / "treesitter/lib"
const sourcePath = incDir() / "treesitter" / "lib"
when defined(Linux):
{.passC: "-std=c11".}
{.passC: "-DUTF8PROC_STATIC".}
{.passC: "-I$1/include" % sourcePath.}
{.passC: "-I$1/src" % sourcePath.}
{.passC: "-I$1/../../utf8proc" % sourcePath.}
{.passC: "-I$1" % (sourcePath / "include").}
{.passC: "-I$1" % (sourcePath / "src").}
{.passC: "-I$1" % (sourcePath / ".." / ".." / "utf8proc").}
{.compile: sourcePath / "src/lib.c".}
{.compile: sourcePath / "src" / "lib.c".}
### Generated below
@ -27,7 +27,7 @@ defineEnum(TSInputEncoding)
defineEnum(TSSymbolType)
defineEnum(TSLogType)
const
headerapi {.used.} = sourcePath / "include/tree_sitter/api.h"
headerapi {.used.} = sourcePath / "include" / "tree_sitter" / "api.h"
TREE_SITTER_LANGUAGE_VERSION* = 9
TSInputEncodingUTF8* = 0.TSInputEncoding
TSInputEncodingUTF16* = 1.TSInputEncoding

View file

@ -5,7 +5,7 @@ import ".."/[setup, paths]
static:
treesitterCSetup()
const srcDir = incDir() / "treesitter_c/src"
const srcDir = incDir() / "treesitter_c" / "src"
import "."/api

View file

@ -5,7 +5,7 @@ import ".."/[setup, paths]
static:
treesitterCppSetup()
const srcDir = incDir() / "treesitter_cpp/src"
const srcDir = incDir() / "treesitter_cpp" / "src"
{.passC: "-I$1" % srcDir.}

View file

@ -13,25 +13,29 @@ when (NimMajor, NimMinor, NimPatch) < (0, 19, 9):
type Time {.importc: "time_t", header: "<time.h>".} = distinct int64
elif defined(posix):
import posix
type time_t* = Time
type
time_t* = Time
wchar_t* {.importc.} = object
else:
import std/time_t as time_t_temp
type time_t* = time_t_temp.Time
when defined(c) or defined(nimdoc):
# http://www.cplusplus.com/reference/cwchar/wchar_t/
# In C++, wchar_t is a distinct fundamental type (and thus it is
# not defined in <cwchar> nor any other header).
type
wchar_t* {.importc, header:"<cwchar>".} = object
elif defined(cpp):
type
wchar_t* {.importc.} = object
type
ptrdiff_t* = ByteAddress
type
va_list* {.importc, header:"<stdarg.h>".} = object
when defined(c):
# http://www.cplusplus.com/reference/cwchar/wchar_t/ In C++, wchar_t is a distinct fundamental type (and thus it is not defined in <cwchar> nor any other header).
type
wchar_t* {.importc, header:"<cwchar>".} = object
elif defined(cpp):
type
wchar_t* {.importc.} = object
template enumOp*(op, typ, typout) =
proc op*(x: typ, y: int): typout {.borrow.}
proc op*(x: int, y: typ): typout {.borrow.}
@ -75,4 +79,4 @@ template defineEnum*(typ) =
proc `/`*(x: typ, y: int): typ = `/`(x, y.typ)
proc `/`*(x: int, y: typ): typ = `/`(x.typ, y)
proc `$` *(x: typ): string {.borrow.}
proc `$` *(x: typ): string {.borrow.}

62
tests/getheader.nims Normal file
View file

@ -0,0 +1,62 @@
import strutils
proc testCall(cmd, output: string, exitCode: int, delete = true) =
if delete:
rmDir("build/liblzma")
rmDir("build/zlib")
echo cmd
var
ccmd =
when defined(windows):
"cmd /c " & cmd
else:
cmd
(outp, exitC) = gorgeEx(ccmd)
echo outp
doAssert exitC == exitCode, $exitC
doAssert outp.contains(output), outp
var
cmd = "nim c -f"
lrcmd = " -r lzma.nim"
zrcmd = " -r zlib.nim"
lexp = "liblzma version = "
zexp = "zlib version = "
testCall(cmd & lrcmd, "No build files found", 1)
when defined(posix):
# stdlib
testCall(cmd & " -d:envTest" & lrcmd, lexp, 0)
testCall(cmd & " -d:envTestStatic" & lrcmd, lexp, 0)
when not defined(osx):
testCall(cmd & " -d:zlibStd" & zrcmd, zexp, 0)
testCall(cmd & " -d:zlibStd -d:zlibStatic" & zrcmd, zexp, 0)
# git
testCall(cmd & " -d:lzmaGit" & lrcmd, lexp, 0)
testCall(cmd & " -d:lzmaGit -d:lzmaStatic" & lrcmd, lexp, 0, delete = false)
# git tag
testCall(cmd & " -d:lzmaGit -d:lzmaSetVer=v5.2.0" & lrcmd, lexp & "5.2.0", 0)
testCall(cmd & " -d:lzmaGit -d:lzmaStatic -d:lzmaSetVer=v5.2.0" & lrcmd, lexp & "5.2.0", 0, delete = false)
testCall("cd build/liblzma && git branch", "v5.2.0", 0, delete = false)
# git
testCall(cmd & " -d:envTest" & zrcmd, zexp, 0)
testCall(cmd & " -d:envTestStatic" & zrcmd, zexp, 0, delete = false)
# git tag
testCall(cmd & " -d:zlibGit -d:zlibSetVer=v1.2.10" & zrcmd, zexp & "1.2.10", 0)
testCall(cmd & " -d:zlibGit -d:zlibStatic -d:zlibSetVer=v1.2.10" & zrcmd, zexp & "1.2.10", 0, delete = false)
testCall("cd build/zlib && git branch", "v1.2.10", 0, delete = false)
# dl
testCall(cmd & " -d:lzmaDL" & lrcmd, "Need version", 1)
testCall(cmd & " -d:lzmaDL -d:lzmaSetVer=5.2.4" & lrcmd, lexp & "5.2.4", 0)
testCall(cmd & " -d:lzmaDL -d:lzmaStatic -d:lzmaSetVer=5.2.4" & lrcmd, lexp & "5.2.4", 0, delete = false)
# dl
testCall(cmd & " -d:zlibDL -d:zlibSetVer=1.2.11" & zrcmd, zexp & "1.2.11", 0)
testCall(cmd & " -d:zlibDL -d:zlibStatic -d:zlibSetVer=1.2.11" & zrcmd, zexp & "1.2.11", 0, delete = false)

46
tests/lzma.nim Normal file
View file

@ -0,0 +1,46 @@
import os, strutils
import nimterop/[build, cimport]
const
baseDir = currentSourcePath.parentDir()/"build"/"liblzma"
static:
cDebug()
when defined(envTest):
setDefines(@["lzmaStd"])
elif defined(envTestStatic):
setDefines(@["lzmaStd", "lzmaStatic"])
getHeader(
"lzma.h",
giturl = "https://github.com/xz-mirror/xz",
dlurl = "https://tukaani.org/xz/xz-$1.tar.gz",
outdir = baseDir,
conFlags = "--disable-xz --disable-xzdec --disable-lzmadec --disable-lzmainfo"
)
cPlugin:
import strutils
proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
sym.name = sym.name.strip(chars = {'_'})
cOverride:
type
lzma_internal = object
lzma_index = object
lzma_index_hash = object
lzma_options_lzma = object
lzma_stream_flags = object
lzma_block = object
lzma_index_iter = object
when not lzmaStatic:
cImport(lzmaPath, recurse = true, dynlib = "lzmaLPath")
else:
cImport(lzmaPath, recurse = true)
echo "liblzma version = " & $lzma_version_string()

View file

@ -1,6 +1,6 @@
import os
import nimterop/[cimport, git, paths]
import nimterop/[cimport, build, paths]
const
baseDir = nimteropBuildDir()/"pcre"

View file

@ -1,4 +1,4 @@
import os, nimterop/[cimport, git, paths]
import os, nimterop/[cimport, build, paths]
const
baseDir = nimteropBuildDir()/"soloud"

73
tests/zlib.nim Normal file
View file

@ -0,0 +1,73 @@
import os, strutils
import nimterop/[build, cimport]
const
baseDir = currentSourcePath.parentDir()/"build"/"zlib"
static:
cDebug()
proc zlibPreBuild(outdir, path: string) =
let
mf = outdir / "Makefile"
if mf.fileExists():
# Delete default Makefile
if mf.readFile().contains("configure first"):
mf.rmFile()
when defined(windows):
# Fix static lib name on Windows
setCmakeLibName(outdir, "zlibstatic", prefix = "lib", oname = "zlib", suffix = ".a")
when defined(envTest):
setDefines(@["zlibGit"])
elif defined(envTestStatic):
setDefines(@["zlibGit", "zlibStatic"])
getHeader(
"zlib.h",
giturl = "https://github.com/madler/zlib",
dlurl = "http://zlib.net/zlib-$1.tar.gz",
outdir = baseDir,
altNames = "z"
)
cPlugin:
import regex, strutils
proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
sym.name = sym.name.replace(re"_[_]+", "_").strip(chars = {'_'})
cOverride:
type
voidpf = ptr object
voidpc = ptr object
voidp = ptr object
uLongf = culong
z_size_t = culong
z_crc_t = culong
alloc_func* {.importc.} = proc(opaque: voidpf, items, size: uint) {.cdecl.}
Bytef {.importc.} = object
when defined(posix):
type
pthread_mutex_s = object
pthread_cond_s = object
pthread_rwlock_arch_t = object
extension = object
fd_set = object
when defined(posix):
static:
cSkipSymbol(@["u_int8_t", "u_int16_t", "u_int32_t", "u_int64_t"])
when zlibGit or zlibDL:
when dirExists(baseDir / "buildcache"):
cIncludeDir(baseDir / "buildcache")
when not zlibStatic:
cImport(zlibPath, recurse = true, dynlib = "zlibLPath")
else:
cImport(zlibPath, recurse = true)
echo "zlib version = " & $zlibVersion()