aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGanesh Viswanathan <dev@genotrance.com>2019-07-18 01:10:32 -0500
committerGanesh Viswanathan <dev@genotrance.com>2019-07-18 01:10:32 -0500
commit79c64d5186728b3683760ac2611d9935d002d280 (patch)
tree8024948523e6e303487c3d88fcd30b19ca1a9e58
parent3bdc321d609c611e1d36ac07b6323d5d786e546c (diff)
downloadnimterop-79c64d5186728b3683760ac2611d9935d002d280.tar.gz
nimterop-79c64d5186728b3683760ac2611d9935d002d280.zip
C2nim, cmake and make support, additional documentation
-rw-r--r--nimterop/cimport.nim98
-rw-r--r--nimterop/git.nim93
2 files changed, 178 insertions, 13 deletions
diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim
index 5717ac3..a4e1761 100644
--- a/nimterop/cimport.nim
+++ b/nimterop/cimport.nim
@@ -124,7 +124,7 @@ proc getNimCheckError(output: string): tuple[tmpFile, errors: string] =
result.errors = "\n\n" & check
-proc getToast(fullpath: string, recurse: bool = false, dynlib: string = ""): string =
+proc getToast(fullpath: string, recurse: bool = false, dynlib: string = "", noNimout = false): string =
var
ret = 0
cmd = when defined(Windows): "cmd /c " else: ""
@@ -132,7 +132,7 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = ""): str
let toastExe = toastExePath()
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.quoteShell &
" make sure 'nimble build' or 'nimble install' built it"
- cmd &= &"{toastExe} --pnim --preprocess"
+ cmd &= &"{toastExe} --preprocess"
if recurse:
cmd.add " --recurse"
@@ -143,17 +143,20 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = ""): str
for i in gStateCT.includeDirs:
cmd.add &" --includeDirs+={i.quoteShell}"
- if dynlib.len != 0:
- cmd.add &" --dynlib={dynlib}"
+ if not noNimout:
+ cmd.add &" --pnim"
+
+ if dynlib.len != 0:
+ cmd.add &" --dynlib={dynlib}"
- if gStateCT.symOverride.len != 0:
- cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
+ if gStateCT.symOverride.len != 0:
+ cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
- when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
- cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
+ when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
+ cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
- if gStateCT.pluginSourcePath.nBl:
- cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
+ if gStateCT.pluginSourcePath.nBl:
+ cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
cmd.add &" {fullpath.quoteShell}"
@@ -542,3 +545,78 @@ macro cImport*(filename: static string, recurse: static bool = false, dynlib: st
(tmpFile, errors) = getNimCheckError(output)
doAssert false, errors & "\n\nNimterop codegen limitation or error - review 'nim check' output above generated for " & tmpFile
+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``
+ ##
+ ## Similar to `cImport() <cimport.html#cImport.m,>`_ but uses ``c2nim`` to generate
+ ## the Nim wrapper instead of ``toast``. Note that neither
+ ## `cOverride() <cimport.html#cOverride.m,>`_, `cSkipSymbol() <cimport.html#cSkipSymbol.m%2Cseq[string]>`_
+ ## nor `cPlugin() <cimport.html#cPlugin.m,>`_ have any impact on ``c2nim``.
+ ##
+ ## ``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.
+ ##
+ ## ``flags`` can be used to pass other command line arguments to ``c2nim``.
+ ##
+ ## ``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.
+
+ result = newNimNode(nnkStmtList)
+
+ let
+ fullpath = findPath(filename)
+
+ echo "# Importing " & fullpath & " with c2nim"
+
+ let
+ output = getToast(fullpath, recurse, dynlib, noNimout = true)
+ hash = output.hash().abs()
+ hpath = getTempDir() / "nimterop_" & $hash & ".h"
+ npath = hpath[0 .. hpath.rfind('.')] & "nim"
+ header = ("header" & fullpath.splitFile().name.replace(re"[-.]+", ""))
+
+ if not fileExists(hpath) or gStateCT.nocache or compileOption("forceBuild"):
+ writeFile(hpath, output)
+
+ doAssert fileExists(hpath), "Unable to write temporary header file: " & hpath
+
+ var
+ cmd = &"c2nim {hpath} --header:{header}"
+
+ if dynlib.len != 0:
+ cmd.add &" --dynlib:{dynlib}"
+ if mode.contains("cpp"):
+ cmd.add " --cpp"
+ if flags.len != 0:
+ cmd.add &" {flags}"
+
+ for i in gStateCT.defines:
+ cmd.add &" --assumedef:{i.quoteShell}"
+
+ let
+ (c2nimout, ret) = gorgeEx(cmd, cache=getCacheValue(hpath))
+ doAssert ret == 0, c2nimout
+
+ var
+ nimout = &"const {header} = \"{fullpath}\"\n\n" & readFile(npath)
+
+ nimout = nimout.
+ replace(re"([u]?int[\d]+)_t", "$1").
+ replace(re"([u]?int)ptr_t", "ptr $1")
+
+ if gStateCT.debug:
+ echo nimout
+
+ try:
+ let body = parseStmt(nimout)
+
+ result.add body
+ except:
+ let
+ (tmpFile, errors) = getNimCheckError(nimout)
+ doAssert false, errors & "\n\nc2nim codegen limitation or error - review 'nim check' output above generated for " & tmpFile
diff --git a/nimterop/git.nim b/nimterop/git.nim
index 01afa9e..6e8fcac 100644
--- a/nimterop/git.nim
+++ b/nimterop/git.nim
@@ -3,6 +3,10 @@ import macros, os, osproc, regex, strformat, strutils
import "."/[paths, 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
@@ -21,12 +25,17 @@ proc execAction*(cmd: string, nostderr=false): string =
doAssert ret == 0, "Command failed: " & $(ret, nostderr) & "\nccmd: " & ccmd & "\nresult:\n" & result
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)
@@ -45,9 +54,12 @@ proc cpFile*(source, dest: string, move=false) =
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 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 " &
@@ -58,6 +70,10 @@ proc extractZip*(zipfile, outdir: string) =
discard execAction(&"cd {outdir.quoteShell} && {cmd % zipfile}")
proc downloadUrl*(url, outdir: string) =
+ ## Download a file using powershell on Windows and curl on other
+ ## systems to the specified output directory
+ ##
+ ## If a zip file, it is automatically extracted after download.
let
file = url.extractFilename()
ext = file.splitFile().ext.toLowerAscii()
@@ -75,6 +91,7 @@ proc downloadUrl*(url, outdir: string) =
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"
@@ -83,6 +100,10 @@ proc gitReset*(outdir: string) =
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 after `cImport()` if required.
echo "# Resetting " & file
let file2 = file.relativePath outdir
let cmd = &"cd {outdir.quoteShell} && git checkout {file2.quoteShell}"
@@ -91,6 +112,10 @@ proc gitCheckout*(file, outdir: string) =
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 sparse checkout. Multiples can be specified one entry per line.
if dirExists(outdir/".git"):
gitReset(outdir)
return
@@ -105,7 +130,8 @@ proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
discard execAction(&"cd {outdirQ} && git remote add origin {url}")
if plist.len != 0:
- # TODO: document this, it's not clear
+ # 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")
@@ -119,7 +145,18 @@ proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
echo "# Pulling repository"
discard execAction(&"cd {outdirQ} && git pull --depth=1 origin master")
-proc configure*(path, check: string) =
+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
@@ -134,5 +171,55 @@ proc configure*(path, check: string) =
if fileExists(path / "configure"):
echo "# Running configure"
- discard execAction(&"cd {path.quoteShell} && bash configure")
+ var
+ cmd = &"cd {path.quoteShell} && bash configure"
+ if flags.len != 0:
+ cmd &= &" {flags}"
+
+ echo execAction(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 " & path
+
+ mkDir(path)
+
+ var
+ cmd = &"cd {path.quoteShell} && cmake {flags}"
+
+ echo execAction(cmd)
+
+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 (path / check).fileExists():
+ return
+
+ echo "# Running make " & path
+
+ var
+ cmd = &"cd {path.quoteShell} && make"
+ if flags.len != 0:
+ cmd &= &" {flags}"
+ echo execAction(cmd) \ No newline at end of file