diff options
| author | Ganesh Viswanathan <dev@genotrance.com> | 2019-07-18 01:10:32 -0500 |
|---|---|---|
| committer | Ganesh Viswanathan <dev@genotrance.com> | 2019-07-18 01:10:32 -0500 |
| commit | 79c64d5186728b3683760ac2611d9935d002d280 (patch) | |
| tree | 8024948523e6e303487c3d88fcd30b19ca1a9e58 | |
| parent | 3bdc321d609c611e1d36ac07b6323d5d786e546c (diff) | |
| download | nimterop-79c64d5186728b3683760ac2611d9935d002d280.tar.gz nimterop-79c64d5186728b3683760ac2611d9935d002d280.zip | |
C2nim, cmake and make support, additional documentation
| -rw-r--r-- | nimterop/cimport.nim | 98 | ||||
| -rw-r--r-- | nimterop/git.nim | 93 |
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 |
