aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgenotrance <dev@genotrance.com>2019-10-03 20:40:05 -0500
committerGitHub <noreply@github.com>2019-10-03 20:40:05 -0500
commitdc222c60115d8cb8ffada8c305fabf1ef8746441 (patch)
treebe8f6d62fb6d3a5d26dd47517cda3a370d918850
parentc3734587a174ea2fc7e19943e6d11d024f06e091 (diff)
parentb12d77c98d6cb4f6a8ad29c47e8964831cb1990d (diff)
downloadnimterop-dc222c60115d8cb8ffada8c305fabf1ef8746441.tar.gz
nimterop-dc222c60115d8cb8ffada8c305fabf1ef8746441.zip
v0.2.0 (#140)
-rw-r--r--.travis.yml16
-rw-r--r--README.md39
-rw-r--r--appveyor.yml47
-rw-r--r--nimterop.nimble60
-rw-r--r--nimterop/all.nim2
-rw-r--r--nimterop/build.nim844
-rw-r--r--nimterop/cimport.nim124
-rw-r--r--nimterop/compat.nim12
-rw-r--r--nimterop/docs.nim47
-rw-r--r--nimterop/getters.nim37
-rw-r--r--nimterop/git.nim295
-rw-r--r--nimterop/grammar.nim9
-rw-r--r--nimterop/setup.nim2
-rw-r--r--nimterop/template.nim7
-rw-r--r--nimterop/templite.nim2
-rw-r--r--nimterop/toast.nim2
-rw-r--r--nimterop/treesitter/api.nim12
-rw-r--r--nimterop/treesitter/c.nim2
-rw-r--r--nimterop/treesitter/cpp.nim2
-rw-r--r--nimterop/types.nim24
-rw-r--r--tests/getheader.nims62
-rw-r--r--tests/lzma.nim46
-rw-r--r--tests/tpcre.nim2
-rw-r--r--tests/tsoloud.nim2
-rw-r--r--tests/zlib.nim73
25 files changed, 1285 insertions, 485 deletions
diff --git a/.travis.yml b/.travis.yml
index f323941..b347f73 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/README.md b/README.md
index 6dfcb04..fcab924 100644
--- a/README.md
+++ b/README.md
@@ -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:
```
diff --git a/appveyor.yml b/appveyor.yml
index 9525673..0d9ba77 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -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
diff --git a/nimterop.nimble b/nimterop.nimble
index 9910444..5825f5a 100644
--- a/nimterop.nimble
+++ b/nimterop.nimble
@@ -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
-
-const htmldocsDir = "build/htmldocs"
+ execTest "tests/tsoloud.nim"
-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()
+ # getHeader tests
+ withDir("tests"):
+ execCmd("nim e getheader.nims")
-task docsPublish, "Generate and publish docs":
- # Uses: pip install ghp-import
- runNimDoc()
- execCmd &"ghp-import --no-jekyll -fp {htmldocsDir}"
+ docsTask()
diff --git a/nimterop/all.nim b/nimterop/all.nim
index 78249a1..c024d01 100644
--- a/nimterop/all.nim
+++ b/nimterop/all.nim
@@ -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]
diff --git a/nimterop/build.nim b/nimterop/build.nim
new file mode 100644
index 0000000..a3dd95f
--- /dev/null
+++ b/nimterop/build.nim
@@ -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`.}
+ )
diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim
index 506f18f..b295344 100644
--- a/nimterop/cimport.nim
+++ b/nimterop/cimport.nim
@@ -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)
diff --git a/nimterop/compat.nim b/nimterop/compat.nim
index 7f89358..1115252 100644
--- a/nimterop/compat.nim
+++ b/nimterop/compat.nim
@@ -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]
diff --git a/nimterop/docs.nim b/nimterop/docs.nim
new file mode 100644
index 0000000..0b46967
--- /dev/null
+++ b/nimterop/docs.nim
@@ -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
diff --git a/nimterop/getters.nim b/nimterop/getters.nim
index 51d63a7..20fd8b3 100644
--- a/nimterop/getters.nim
+++ b/nimterop/getters.nim
@@ -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
diff --git a/nimterop/git.nim b/nimterop/git.nim
index 72315a6..7a0cf2a 100644
--- a/nimterop/git.nim
+++ b/nimterop/git.nim
@@ -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 \ No newline at end of file
diff --git a/nimterop/grammar.nim b/nimterop/grammar.nim
index a66442f..bfc5def 100644
--- a/nimterop/grammar.nim
+++ b/nimterop/grammar.nim
@@ -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":
diff --git a/nimterop/setup.nim b/nimterop/setup.nim
index 8e70e64..4ee375b 100644
--- a/nimterop/setup.nim
+++ b/nimterop/setup.nim
@@ -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", """
diff --git a/nimterop/template.nim b/nimterop/template.nim
index 1c9fc7c..f625280 100644
--- a/nimterop/template.nim
+++ b/nimterop/template.nim
@@ -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()
diff --git a/nimterop/templite.nim b/nimterop/templite.nim
index 71262df..7d8eb5b 100644
--- a/nimterop/templite.nim
+++ b/nimterop/templite.nim
@@ -1,6 +1,6 @@
import os, strutils
-import nimterop/[cimport, git, paths]
+import nimterop/[cimport, build, paths]
const
baseDir = currentSourcePath.parentDir()/"build"
diff --git a/nimterop/toast.nim b/nimterop/toast.nim
index 6e38670..b66a3c6 100644
--- a/nimterop/toast.nim
+++ b/nimterop/toast.nim
@@ -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
diff --git a/nimterop/treesitter/api.nim b/nimterop/treesitter/api.nim
index 62ebe3b..2c11a60 100644
--- a/nimterop/treesitter/api.nim
+++ b/nimterop/treesitter/api.nim
@@ -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
diff --git a/nimterop/treesitter/c.nim b/nimterop/treesitter/c.nim
index a39ee65..38fa003 100644
--- a/nimterop/treesitter/c.nim
+++ b/nimterop/treesitter/c.nim
@@ -5,7 +5,7 @@ import ".."/[setup, paths]
static:
treesitterCSetup()
-const srcDir = incDir() / "treesitter_c/src"
+const srcDir = incDir() / "treesitter_c" / "src"
import "."/api
diff --git a/nimterop/treesitter/cpp.nim b/nimterop/treesitter/cpp.nim
index 1b47206..131e57f 100644
--- a/nimterop/treesitter/cpp.nim
+++ b/nimterop/treesitter/cpp.nim
@@ -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.}
diff --git a/nimterop/types.nim b/nimterop/types.nim
index 5b4ad5f..8492f00 100644
--- a/nimterop/types.nim
+++ b/nimterop/types.nim
@@ -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.} \ No newline at end of file
+ proc `$` *(x: typ): string {.borrow.}
diff --git a/tests/getheader.nims b/tests/getheader.nims
new file mode 100644
index 0000000..c122484
--- /dev/null
+++ b/tests/getheader.nims
@@ -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)
diff --git a/tests/lzma.nim b/tests/lzma.nim
new file mode 100644
index 0000000..2368b55
--- /dev/null
+++ b/tests/lzma.nim
@@ -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()
diff --git a/tests/tpcre.nim b/tests/tpcre.nim
index 705525f..5588850 100644
--- a/tests/tpcre.nim
+++ b/tests/tpcre.nim
@@ -1,6 +1,6 @@
import os
-import nimterop/[cimport, git, paths]
+import nimterop/[cimport, build, paths]
const
baseDir = nimteropBuildDir()/"pcre"
diff --git a/tests/tsoloud.nim b/tests/tsoloud.nim
index f46dd10..12966a8 100644
--- a/tests/tsoloud.nim
+++ b/tests/tsoloud.nim
@@ -1,4 +1,4 @@
-import os, nimterop/[cimport, git, paths]
+import os, nimterop/[cimport, build, paths]
const
baseDir = nimteropBuildDir()/"soloud"
diff --git a/tests/zlib.nim b/tests/zlib.nim
new file mode 100644
index 0000000..371d41f
--- /dev/null
+++ b/tests/zlib.nim
@@ -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()