Fix #127 - write wrapper to file

This commit is contained in:
Ganesh Viswanathan 2020-06-30 22:33:10 -05:00
commit 2aef7c99b3
4 changed files with 100 additions and 85 deletions

View file

@ -15,13 +15,13 @@ Refer to the documentation for `getHeader()` for details on how to use this new
See the full list of changes here:
https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.0
https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.1
### Breaking changes
- The legacy algorithm has been removed as promised. `ast2` is now the default and wrappers no longer need to explicitly specify `-f:ast2` in order to use it.
- All shared libraries installed by `getHeader()` will now get copied into the `libdir` parameter specified. If left blank, `libdir` will default to the directory where the executable binary gets created (outdir). While this is not really a breaking change, it is a change in behavior compared to older versions of nimterop. Note that `Std` libraries are not copied over. [#154](i154)
- All shared libraries installed by `getHeader()` will now get copied into the `libdir` parameter specified. If left blank, `libdir` will default to the directory where the executable binary gets created (outdir). While this is not really a breaking change, it is a change in behavior compared to older versions of nimterop. Note that `Std` libraries are not copied over. [#154][i154]
- `git.nim` has been removed. This module was an artifact from the early days and was renamed to `build.nim` back in v0.2.0.
@ -33,11 +33,13 @@ https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.0
- `gitPull()` now checks if an existing repository is at the `checkout` value specified. If not, it will pull the latest changes and checkout the specified commit, tag or branch.
- `cImport()` can now write the generated wrapper output to a user-defined file with the `nimFile` param. [#127][i127] (since v0.6.1)
### Other improvements
- Generated wrappers no longer depend on nimterop being present - no more `import nimterop/types`. Supporting code is directly included in the wrapper output and only when required. E.g. enum macro is only included if wrapper contains enums. [#125](i125) (since v0.6.1)
- Generated wrappers no longer depend on nimterop being present - no more `import nimterop/types`. Supporting code is directly included in the wrapper output and only when required. E.g. enum macro is only included if wrapper contains enums. [#125][i125] (since v0.6.1)
- `cImport()` now includes wrapper output from a file rather than inline. Errors in generated wrappers will no longer point to a line in `macros.nim` making debugging easier.
- `cImport()` now includes wrapper output from a file rather than inline. Errors in generated wrappers will no longer point to a line in `macros.nim` making debugging easier. (since v0.6.1)
## Version 0.5.0
@ -50,7 +52,7 @@ Version 0.6.0 of Nimterop will make `ast2` the default backend and the legacy al
See the full list of changes here:
https://github.com/nimterop/nimterop/compare/v0.4.4...v0.5.0
https://github.com/nimterop/nimterop/compare/v0.4.4...v0.5.4
### Breaking changes
@ -108,6 +110,8 @@ https://github.com/nimterop/nimterop/compare/v0.4.4...v0.5.0
[i54]: https://github.com/nimterop/nimterop/issues/54
[i74]: https://github.com/nimterop/nimterop/issues/74
[i76]: https://github.com/nimterop/nimterop/issues/76
[i125]: https://github.com/nimterop/nimterop/issues/125
[i127]: https://github.com/nimterop/nimterop/issues/127
[i137]: https://github.com/nimterop/nimterop/issues/137
[i147]: https://github.com/nimterop/nimterop/issues/147
[i148]: https://github.com/nimterop/nimterop/issues/148

View file

@ -50,14 +50,10 @@ task minitest, "Test for Nim CI":
exec "nim c -f -d:checkAbi -r tests/tast2.nim"
exec "nim c -f -d:checkAbi -d:zlibStd -d:zlibDL -d:zlibSetVer=1.2.11 -r tests/zlib.nim"
task test, "Test":
rmFile("tests/timeit.txt")
buildTimeitTask()
buildToastTask()
task basic, "Basic tests":
execTest "tests/tast2.nim"
execTest "tests/tast2.nim", "-d:NOHEADER"
execTest "tests/tast2.nim", "-d:NOHEADER -d:WRAPPED"
execTest "tests/tnimterop_c.nim"
execTest "tests/tnimterop_c.nim", "-d:FLAGS=\"-H\""
@ -65,6 +61,7 @@ task test, "Test":
execCmd "nim cpp --hints:off -f -r tests/tnimterop_cpp.nim"
execCmd "./nimterop/toast tests/toast.cfg tests/include/toast.h"
task wrapper, "Wrapper tests":
execTest "tests/tpcre.nim"
when defined(Linux):
@ -79,12 +76,29 @@ task test, "Test":
execTest "tests/tsoloud.nim"
execTest "tests/tsoloud.nim", "-d:FLAGS=\"-H\""
# getHeader tests
task getheader, "getHeader tests":
withDir("tests"):
exec "nim e getheader.nims"
if not existsEnv("APPVEYOR"):
task package, "Wrapper package tests":
if not existsEnv("APPVEYOR"):
withDir("tests"):
exec "nim e wrappers.nims"
task test, "Test":
rmFile("tests/timeit.txt")
buildTimeitTask()
buildToastTask()
basicTask()
wrapperTask()
getheaderTask()
packageTask()
docsTask()
echo readFile("tests/timeit.txt")

View file

@ -70,6 +70,9 @@ proc walkDirImpl(indir, inext: string, file=true): seq[string] =
if ret == 0:
result = output.splitLines()
proc fixRelFile(file: string): string =
if file.isAbsolute(): file else: getProjectDir() / file
proc getCacheValue(fullpath: string): string =
if not gStateCT.nocache:
result = fullpath.getFileDate()
@ -102,7 +105,7 @@ proc getNimCheckError(nimFile: string) =
"Codegen limitation or error - review 'nim check' output above generated for " & nimFile
proc getToast(fullpaths: seq[string], recurse: bool = false, dynlib: string = "",
mode = "c", flags = "", noNimout = false): string =
mode = "c", flags = "", outFile = "", noNimout = false): string =
var
cmd = when defined(Windows): "cmd /c " else: ""
ext = "h"
@ -148,18 +151,17 @@ proc getToast(fullpaths: seq[string], recurse: bool = false, dynlib: string = ""
for fullpath in fullpaths:
cmd.add &" {fullpath.sanitizePath}"
# Generate filename for toast output
let
hash = (cmd & cacheKey).hash().abs()
cachePath = getNimteropCacheDir() / "toastCache" / "nimterop_" & $hash
result = if outFile.nBl: fixRelFile(outFile) else:
# Generate filename for toast output if not specified
getNimteropCacheDir() / "toastCache" / "nimterop_" &
($(cmd & cacheKey).hash().abs()).addFileExt(ext)
result = cachePath.addFileExt(ext)
when defined(Windows):
result = result.replace(DirSep, '/')
if not fileExists(result) or compileOption("forceBuild"):
let
dir = cachePath.parentDir()
dir = result.parentDir()
if not dirExists(dir):
mkDir(dir)
@ -171,9 +173,8 @@ proc getToast(fullpaths: seq[string], recurse: bool = false, dynlib: string = ""
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
## `cOverride() <cimport.html#cOverride.m>`_ macro block so that Nimterop uses
## these definitions instead.
## accurate, it may be required to hand wrap them. Define them in a `cOverride()`
## macro block so that Nimterop uses these definitions instead.
##
## For example:
##
@ -194,9 +195,9 @@ macro cOverride*(body): untyped =
## cOverride:
## proc svGetCallerInfo(fileName: var cstring; lineNumber: var cint)
##
## Using the `cOverride() <cimport.html#cOverride.m>`_ block, nimterop
## can be instructed to use this definition of `svGetCallerInfo()` instead.
## This works for procs, consts and types.
## Using the `cOverride()` block, nimterop can be instructed to use this
## definition of `svGetCallerInfo()` instead. This works for procs, consts
## and types.
##
## `cOverride()` only affects the next `cImport()` call. This is because any
## recognized symbols get overridden in place and any remaining symbols get
@ -263,11 +264,10 @@ proc onSymbolOverride*(sym: var Symbol) {.exportc, dynlib.} =
decho "Overriding " & names.join(" ")
proc cSkipSymbol*(skips: seq[string]) {.compileTime.} =
## Similar to `cOverride() <cimport.html#cOverride.m>`_, this macro allows
## filtering out symbols not of interest from the generated output.
## Similar to `cOverride()`, this macro allows filtering out symbols not of
## interest from the generated output.
##
## `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_ only affects calls to
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ that follow it.
## `cSkipSymbol()` only affects calls to `cImport()` that follow it.
runnableExamples:
static: cSkipSymbol @["proc1", "Type2"]
gStateCT.symOverride.add skips
@ -291,11 +291,9 @@ proc cPluginHelper(body: string, imports = "import macros, nimterop/plugin\n\n")
gStateCT.pluginSourcePath = path
macro cPlugin*(body): untyped =
## 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.
## When `cOverride()` and `cSkipSymbol()` are not adequate, the `cPlugin()`
## macro can be used to customize the generated Nim output. The following
## callbacks are available at this time.
##
## .. code-block:: nim
##
@ -325,9 +323,7 @@ macro cPlugin*(body): untyped =
## `macros` and `nimterop/plugins` are 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.
## `cPlugin()` only affects calls to `cImport()` that follow it.
runnableExamples:
cPlugin:
import strutils
@ -367,14 +363,10 @@ macro cPluginPath*(path: static[string]): untyped =
proc cSearchPath*(path: string): string {.compileTime.}=
## Get full path to file or directory `path` in search path configured
## using `cAddSearchDir() <cimport.html#cAddSearchDir%2Cstring>`_ and
## `cAddStdDir() <cimport.html#cAddStdDir,string>`_.
## using `cAddSearchDir()` and `cAddStdDir()`.
##
## This can be used to locate files or directories that can be passed onto
## `cCompile() <cimport.html#cCompile.m%2C%2Cstring%2Cstring>`_,
## `cIncludeDir() <cimport.html#cIncludeDir.m>`_ and
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_.
## `cCompile()`, `cIncludeDir()` and `cImport()`.
result = findPath(path, fail = false)
if result.Bl:
var found = false
@ -394,21 +386,17 @@ 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>`_
## 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.
## `cImport()` change and affect the generated content, they will be ignored
## and the cached value will continue to be used . Use `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` 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
## 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".}`
## called within `cImport()` or `c2nImport()` as well as to the C/C++
## compiler during Nim compilation using `{.passC: "-DXXX".}`
result = newNimNode(nnkStmtList)
var str = name
@ -429,7 +417,7 @@ macro cDefine*(name: static string, val: static string = ""): untyped =
proc cAddSearchDir*(dir: string) {.compileTime.} =
## Add directory `dir` to the search path used in calls to
## `cSearchPath() <cimport.html#cSearchPath,string>`_.
## `cSearchPath()`.
runnableExamples:
import nimterop/paths, os
static:
@ -441,10 +429,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".}`.
## called within `cImport()` or `c2nImport()` as well as to the C/C++
## compiler during Nim compilation using `{.passC: "-IXXX".}`.
var dir = interpPath(dir)
result = newNimNode(nnkStmtList)
@ -459,7 +445,7 @@ macro cIncludeDir*(dir: static string): untyped =
proc cAddStdDir*(mode = "c") {.compileTime.} =
## Add the standard `c` [default] or `cpp` include paths to search
## path used in calls to `cSearchPath() <cimport.html#cSearchPath,string>`_
## path used in calls to `cSearchPath()`.
runnableExamples:
static: cAddStdDir()
import os
@ -561,7 +547,7 @@ macro cCompile*(path: static string, mode = "c", exclude = ""): untyped =
gecho result.repr
macro cImport*(filenames: static seq[string], recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
mode: static string = "c", flags: static string = "", nimFile: static string = ""): untyped =
## Import multiple headers in one shot
##
## This macro is preferable over multiple individual `cImport()` calls, especially
@ -581,7 +567,7 @@ macro cImport*(filenames: static seq[string], recurse: static bool = false, dynl
gecho "# Importing " & fullpaths.join(", ").sanitizePath
let
nimFile = getToast(fullpaths, recurse, dynlib, mode, flags)
nimFile = getToast(fullpaths, recurse, dynlib, mode, flags, nimFile)
# Reset plugin and overrides for next cImport
if gStateCT.overrides.nBl:
@ -600,16 +586,15 @@ macro cImport*(filenames: static seq[string], recurse: static bool = false, dynl
getNimCheckError(nimFile)
macro cImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
mode: static string = "c", flags: static string = "", nimFile: 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`
## can also be used after Nim v0.19.4 to flush the cache.
## `cDisableCaching()` is set. `nim -f` can also be used 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
## `cIncludeDir() <cimport.html#cIncludeDir.m>`_
## `cIncludeDir()`.
##
## `dynlib` can be used to specify the Nim string to use to specify the dynamic
## library to load the imported symbols from. For example:
@ -630,9 +615,9 @@ 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()`, or the `{.passL.}` pragma can be used to specify the
## static lib to link.
##
## `mode` selects the preprocessor and tree-sitter parser to be used to process
## the header.
@ -641,33 +626,41 @@ macro cImport*(filename: static string, recurse: static bool = false, dynlib: st
## good example would be `--prefix` and `--suffix` which strip leading and
## trailing strings from identifiers, `_` being quite common.
##
## `nimFile` is the location where the generated wrapper should get written.
## By default, the generated wrapper is written to `nimcache` and included from
## there. `nimFile` makes it possible to write the wrapper to a predetermined
## location which can then be directly imported into the main application and
## checked into source control if preferred. Importing the nimterop wrapper with
## `nimFile` specified still works per usual. If `nimFile` is not an absolute
## path, it is relative to the project path.
##
## `cImport()` consumes and resets preceding `cOverride()` calls. `cPlugin()`
## is retained for the next `cImport()` call unless a new `cPlugin()` call is
## defined.
return quote do:
cImport(@[`filename`], bool(`recurse`), `dynlib`, `mode`, `flags`)
cImport(@[`filename`], bool(`recurse`), `dynlib`, `mode`, `flags`, `nimFile`)
macro c2nImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
mode: static string = "c", flags: static string = "", nimFile: static string = ""): untyped =
## 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
## `cOverride() <cimport.html#cOverride.m>`_, `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## nor `cPlugin() <cimport.html#cPlugin.m>`_ have any impact on `c2nim`.
## Similar to `cImport()` but uses `c2nim` to generate the Nim wrapper instead
## of `toast`. Note that neither `cOverride()`, `cSkipSymbol()` nor `cPlugin()`
## have any impact on `c2nim`.
##
## `toast` is only used to preprocess the header file and recurse
## if specified.
## `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.
## `nimFile` is the location where the generated wrapper should get written,
## similar to `cImport()`.
##
## `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
@ -677,13 +670,13 @@ macro c2nImport*(filename: static string, recurse: static bool = false, dynlib:
let
hFile = getToast(@[fullpath], recurse, dynlib, mode, noNimout = true)
nimFile = hFile.changeFileExt("nim")
nimFile = if nimFile.nBl: fixRelFile(nimFile) else: hFile.changeFileExt("nim")
header = "header" & fullpath.splitFile().name.split(seps = {'-', '.'}).join()
if not fileExists(nimFile) or compileOption("forceBuild"):
var
cmd = when defined(Windows): "cmd /c " else: ""
cmd &= &"c2nim {hFile} --header:{header}"
cmd &= &"c2nim {hFile} --header:{header} --out:{nimFile.sanitizePath}"
if dynlib.nBl:
cmd.add &" --dynlib:{dynlib}"

View file

@ -38,7 +38,11 @@ cOverride:
A1* = A0
cDefine("SOME_CONST=100")
cImport(path, flags="-f:ast2 -ENK_,SDL_ -GVICE=SLICE -TMyInt=cint" & flags)
when not defined(WRAPPED):
cImport(path, flags="-f:ast2 -ENK_,SDL_ -GVICE=SLICE -TMyInt=cint" & flags, nimFile = "tast2wrapped.nim")
else:
import tast2wrapped
proc getPragmas(n: NimNode): HashSet[string] =
# Find all pragmas in AST, return as "name" or "name:value" in set