C2nim, cmake and make support, additional documentation

This commit is contained in:
Ganesh Viswanathan 2019-07-18 01:10:32 -05:00
commit 79c64d5186
2 changed files with 178 additions and 13 deletions

View file

@ -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 gStateCT.symOverride.len != 0:
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
if dynlib.len != 0:
cmd.add &" --dynlib={dynlib}"
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
if gStateCT.symOverride.len != 0:
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
if gStateCT.pluginSourcePath.nBl:
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
cmd.add &" --nim:{getCurrentCompilerExe().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

View file

@ -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)