diff options
| author | Ganesh Viswanathan <dev@genotrance.com> | 2019-08-23 07:10:54 -0700 |
|---|---|---|
| committer | Ganesh Viswanathan <dev@genotrance.com> | 2019-10-02 15:30:36 -0500 |
| commit | 7ef73147a69f224f3df36081afcc7a235cd70b33 (patch) | |
| tree | c72d184bd41442a54f056cb4c268e3eb79ee8314 | |
| parent | 5b0a5ab14611f0f562ae96c75ee5d7ca634ae2ea (diff) | |
| download | nimterop-7ef73147a69f224f3df36081afcc7a235cd70b33.tar.gz nimterop-7ef73147a69f224f3df36081afcc7a235cd70b33.zip | |
getHeader and supporting procs
| -rw-r--r-- | nimterop/build.nim | 197 | ||||
| -rw-r--r-- | nimterop/cimport.nim | 42 |
2 files changed, 210 insertions, 29 deletions
diff --git a/nimterop/build.nim b/nimterop/build.nim index 72315a6..342e6bc 100644 --- a/nimterop/build.nim +++ b/nimterop/build.nim @@ -1,4 +1,6 @@ -import os, osproc, strformat, strutils +import macros, osproc, sequtils, strformat, strutils + +import os except findExe import "."/[compat] @@ -292,3 +294,196 @@ proc make*(path, check: string, flags = "") = echo execAction(cmd) doAssert (path / check).fileExists(), "# make failed" + +proc findFile*(file, dir: string): string = + ## Find the file in the specified directory + for f in walkDirRec(dir): + if f.extractFilename() == file: + if result.len == 0 or result.len > f.len: + result = f + +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, ret) = 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: + result.add line.strip() + +proc getStdPath(header: string): string = + for inc in getGccPaths(): + result = findFile(header, inc) + 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 buildLibrary(outdir, conFlags, conStaticLib, conDynLib, cmakeFlags, cmakeStaticLib, cmakeDynLib, makeFlags: string) = + var + conDeps = false + conDepStr = "" + cmakeDeps = false + cmakeDepStr = "" + + if fileExists(outdir / "CMakeLists.txt"): + if findExe("cmake").len != 0: + if cmakeStaticLib.len != 0 or cmakeDynLib.len != 0: + var + gen = "" + when defined(windows): + if findExe("sh").len != 0: + gen = "MSYS Makefiles" + else: + gen = "MinGW Makefiles" + else: + gen = "Unix Makefiles" + cmake(outdir / "build", "Makefile", &".. -G {gen.quoteShell} {cmakeFlags}") + cmakeDeps = true + let + check = if cmakeStaticLib.len != 0: cmakeStaticLib else: cmakeDynLib + make(outdir / "build", check, makeFlags) + else: + cmakeDepStr &= "cmakeStatibLib / cmakeDynLib not specified" + else: + cmakeDepStr &= "cmake executable missing" + + template cfgCommon() {.dirty.} = + if (conStaticLib.len != 0 or conDynLib.len != 0): + configure(outdir, "Makefile", conFlags) + conDeps = true + let + check = if conStaticLib.len != 0: conStaticLib else: conDynLib + make(outdir, check, makeFlags) + else: + conDepStr &= "conStaticLib / conDynLib not specified" + + if not cmakeDeps: + if not fileExists(outdir / "configure"): + if fileExists(outdir / "autogen.sh") or fileExists(outdir / "build" / "autogen.sh"): + if findExe("aclocal").len != 0: + if findExe("autoconf").len != 0: + if findExe("libtoolize").len != 0: + cfgCommon() + else: + conDepStr &= "libtoolize executable missing" + else: + conDepStr &= "autoconf executable missing" + else: + conDepStr &= "aclocal executable missing" + else: + if findExe("bash").len != 0: + cfgCommon() + else: + conDepStr &= "bash executable missing" + + 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, &"\n# Build configuration failed - {error}\n" + +macro getHeader*(header: static[string], giturl: static[string] = "", dlurl: static[string] = "", outdir: static[string] = "", + conFlags: static[string] = "", conStaticLib: static[string] = "", conDynLib: static[string] = "", + cmakeFlags: static[string] = "", cmakeStaticLib: static[string] = "", cmakeDynLib: static[string] = "", + makeFlags: 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>`_. + ## + ## Checks defines based on the header name (e.g. lzma from lzma.h), to use 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 defines are specified, ``outdir`` will be searched for the header. + ## + ## The library is then configured (either with cmake or autotools if possible) and then built + ## using make. + var + name = header.split(".")[0] + + stdName = newIdentNode(name & "Std") + gitName = newIdentNode(name & "Git") + dlName = newIdentNode(name & "DL") + + path = newIdentNode(name & "Path") + version = newIdentNode(name & "Version") + + result = newNimNode(nnkStmtList) + result.add(quote do: + const `version`* {.strdefine.} = "" + + when defined(`stdName`): + const `path`* = getStdPath(`header`) + else: + const `path`* = + when defined(`gitName`): + getGitPath(`header`, `giturl`, `outdir`, `version`) + elif defined(`dlName`): + getDlPath(`header`, `dlurl`, `outdir`, `version`) + else: + getLocalPath(`header`, `outdir`) + + static: + doAssert `path`.len != 0, "\nHeader " & `header` & " not found - " & "missing/empty outdir or -d:$1Std -d:$1Git or -d:$1DL not specified" % `name` + + buildLibrary(`outdir`, `conFlags`, `conStaticLib`, `conDynLib`, `cmakeFlags`, `cmakeStaticLib`, `cmakeDynLib`, `makeFlags`) + ) diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim index 0e875ed..fc03bc5 100644 --- a/nimterop/cimport.nim +++ b/nimterop/cimport.nim @@ -168,14 +168,6 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = "", (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 @@ -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. @@ -322,7 +315,8 @@ 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. @@ -334,8 +328,8 @@ proc cDisableCaching*() {.compileTime.} = 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".}`` + ## 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) @@ -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) @@ -392,16 +386,8 @@ proc cAddStdDir*(mode = "c") {.compileTime.} = 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.}`` @@ -528,8 +514,8 @@ 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. + ## 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 ## be ignored for the foreseeable future. @@ -562,8 +548,8 @@ macro c2nImport*(filename: static string, recurse: static bool = false, dynlib: 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%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``. ## |
