From 2aef7c99b36bbc9db9bcbcd62c06c6a5e2d4d0c0 Mon Sep 17 00:00:00 2001 From: Ganesh Viswanathan Date: Tue, 30 Jun 2020 22:33:10 -0500 Subject: [PATCH] Fix #127 - write wrapper to file --- CHANGES.md | 14 +++-- nimterop.nimble | 30 +++++++--- nimterop/cimport.nim | 135 ++++++++++++++++++++----------------------- tests/tast2.nim | 6 +- 4 files changed, 100 insertions(+), 85 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3488252..43f1d34 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/nimterop.nimble b/nimterop.nimble index ebc5f0a..628ef40 100644 --- a/nimterop.nimble +++ b/nimterop.nimble @@ -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") diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim index 109b172..d992c04 100644 --- a/nimterop/cimport.nim +++ b/nimterop/cimport.nim @@ -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() `_ 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() `_ 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() `_, 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() `_ only affects calls to - ## `cImport() `_ 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() `_ 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. + ## 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() `_ only affects calls to - ## `cImport() `_ 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() `_ and - ## `cAddStdDir() `_. + ## using `cAddSearchDir()` and `cAddStdDir()`. ## ## This can be used to locate files or directories that can be passed onto - ## `cCompile() `_, - ## `cIncludeDir() `_ and - ## `cImport() `_. - + ## `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() `_ - ## 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. + ## `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() `_ - ## or `c2nImport() `_ - ## 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() `_. + ## `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() `_ - ## or `c2nImport() `_ - ## 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() `_ + ## 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() `_ 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() `_ + ## `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() `_, 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() `_ - ## but uses `c2nim` to generate the Nim wrapper instead of `toast`. Note that neither - ## `cOverride() `_, `cSkipSymbol() `_ - ## nor `cPlugin() `_ 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}" diff --git a/tests/tast2.nim b/tests/tast2.nim index 3f626ae..669ec98 100644 --- a/tests/tast2.nim +++ b/tests/tast2.nim @@ -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