C2nim, cmake and make support, additional documentation
This commit is contained in:
parent
3bdc321d60
commit
79c64d5186
2 changed files with 178 additions and 13 deletions
|
|
@ -124,7 +124,7 @@ proc getNimCheckError(output: string): tuple[tmpFile, errors: string] =
|
||||||
|
|
||||||
result.errors = "\n\n" & check
|
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
|
var
|
||||||
ret = 0
|
ret = 0
|
||||||
cmd = when defined(Windows): "cmd /c " else: ""
|
cmd = when defined(Windows): "cmd /c " else: ""
|
||||||
|
|
@ -132,7 +132,7 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = ""): str
|
||||||
let toastExe = toastExePath()
|
let toastExe = toastExePath()
|
||||||
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.quoteShell &
|
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.quoteShell &
|
||||||
" make sure 'nimble build' or 'nimble install' built it"
|
" make sure 'nimble build' or 'nimble install' built it"
|
||||||
cmd &= &"{toastExe} --pnim --preprocess"
|
cmd &= &"{toastExe} --preprocess"
|
||||||
|
|
||||||
if recurse:
|
if recurse:
|
||||||
cmd.add " --recurse"
|
cmd.add " --recurse"
|
||||||
|
|
@ -143,17 +143,20 @@ proc getToast(fullpath: string, recurse: bool = false, dynlib: string = ""): str
|
||||||
for i in gStateCT.includeDirs:
|
for i in gStateCT.includeDirs:
|
||||||
cmd.add &" --includeDirs+={i.quoteShell}"
|
cmd.add &" --includeDirs+={i.quoteShell}"
|
||||||
|
|
||||||
if dynlib.len != 0:
|
if not noNimout:
|
||||||
cmd.add &" --dynlib={dynlib}"
|
cmd.add &" --pnim"
|
||||||
|
|
||||||
if gStateCT.symOverride.len != 0:
|
if dynlib.len != 0:
|
||||||
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
|
cmd.add &" --dynlib={dynlib}"
|
||||||
|
|
||||||
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
|
if gStateCT.symOverride.len != 0:
|
||||||
cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
|
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
|
||||||
|
|
||||||
if gStateCT.pluginSourcePath.nBl:
|
when (NimMajor, NimMinor, NimPatch) >= (0, 19, 9):
|
||||||
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
|
cmd.add &" --nim:{getCurrentCompilerExe().quoteShell}"
|
||||||
|
|
||||||
|
if gStateCT.pluginSourcePath.nBl:
|
||||||
|
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.quoteShell}"
|
||||||
|
|
||||||
cmd.add &" {fullpath.quoteShell}"
|
cmd.add &" {fullpath.quoteShell}"
|
||||||
|
|
||||||
|
|
@ -542,3 +545,78 @@ macro cImport*(filename: static string, recurse: static bool = false, dynlib: st
|
||||||
(tmpFile, errors) = getNimCheckError(output)
|
(tmpFile, errors) = getNimCheckError(output)
|
||||||
doAssert false, errors & "\n\nNimterop codegen limitation or error - review 'nim check' output above generated for " & tmpFile
|
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
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ import macros, os, osproc, regex, strformat, strutils
|
||||||
import "."/[paths, compat]
|
import "."/[paths, compat]
|
||||||
|
|
||||||
proc execAction*(cmd: string, nostderr=false): string =
|
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
|
var
|
||||||
ccmd = ""
|
ccmd = ""
|
||||||
ret = 0
|
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
|
doAssert ret == 0, "Command failed: " & $(ret, nostderr) & "\nccmd: " & ccmd & "\nresult:\n" & result
|
||||||
|
|
||||||
proc mkDir*(dir: string) =
|
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):
|
if not dirExists(dir):
|
||||||
let
|
let
|
||||||
flag = when not defined(Windows): "-p" else: ""
|
flag = when not defined(Windows): "-p" else: ""
|
||||||
discard execAction(&"mkdir {flag} {dir.quoteShell}")
|
discard execAction(&"mkdir {flag} {dir.quoteShell}")
|
||||||
|
|
||||||
proc cpFile*(source, dest: string, move=false) =
|
proc cpFile*(source, dest: string, move=false) =
|
||||||
|
## Copy a file from source to destination at compile time
|
||||||
let
|
let
|
||||||
source = source.replace("/", $DirSep)
|
source = source.replace("/", $DirSep)
|
||||||
dest = dest.replace("/", $DirSep)
|
dest = dest.replace("/", $DirSep)
|
||||||
|
|
@ -45,9 +54,12 @@ proc cpFile*(source, dest: string, move=false) =
|
||||||
discard execAction(&"{cmd} {source.quoteShell} {dest.quoteShell}")
|
discard execAction(&"{cmd} {source.quoteShell} {dest.quoteShell}")
|
||||||
|
|
||||||
proc mvFile*(source, dest: string) =
|
proc mvFile*(source, dest: string) =
|
||||||
|
## Move a file from source to destination at compile time
|
||||||
cpFile(source, dest, move=true)
|
cpFile(source, dest, move=true)
|
||||||
|
|
||||||
proc extractZip*(zipfile, outdir: string) =
|
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 $#"
|
var cmd = "unzip -o $#"
|
||||||
if defined(Windows):
|
if defined(Windows):
|
||||||
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
|
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
|
||||||
|
|
@ -58,6 +70,10 @@ proc extractZip*(zipfile, outdir: string) =
|
||||||
discard execAction(&"cd {outdir.quoteShell} && {cmd % zipfile}")
|
discard execAction(&"cd {outdir.quoteShell} && {cmd % zipfile}")
|
||||||
|
|
||||||
proc downloadUrl*(url, outdir: string) =
|
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
|
let
|
||||||
file = url.extractFilename()
|
file = url.extractFilename()
|
||||||
ext = file.splitFile().ext.toLowerAscii()
|
ext = file.splitFile().ext.toLowerAscii()
|
||||||
|
|
@ -75,6 +91,7 @@ proc downloadUrl*(url, outdir: string) =
|
||||||
extractZip(file, outdir)
|
extractZip(file, outdir)
|
||||||
|
|
||||||
proc gitReset*(outdir: string) =
|
proc gitReset*(outdir: string) =
|
||||||
|
## Hard reset the git repository at the specified directory
|
||||||
echo "# Resetting " & outdir
|
echo "# Resetting " & outdir
|
||||||
|
|
||||||
let cmd = &"cd {outdir.quoteShell} && git reset --hard"
|
let cmd = &"cd {outdir.quoteShell} && git reset --hard"
|
||||||
|
|
@ -83,6 +100,10 @@ proc gitReset*(outdir: string) =
|
||||||
echo "# Retrying ..."
|
echo "# Retrying ..."
|
||||||
|
|
||||||
proc gitCheckout*(file, outdir: string) =
|
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
|
echo "# Resetting " & file
|
||||||
let file2 = file.relativePath outdir
|
let file2 = file.relativePath outdir
|
||||||
let cmd = &"cd {outdir.quoteShell} && git checkout {file2.quoteShell}"
|
let cmd = &"cd {outdir.quoteShell} && git checkout {file2.quoteShell}"
|
||||||
|
|
@ -91,6 +112,10 @@ proc gitCheckout*(file, outdir: string) =
|
||||||
echo "# Retrying ..."
|
echo "# Retrying ..."
|
||||||
|
|
||||||
proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
|
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"):
|
if dirExists(outdir/".git"):
|
||||||
gitReset(outdir)
|
gitReset(outdir)
|
||||||
return
|
return
|
||||||
|
|
@ -105,7 +130,8 @@ proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
|
||||||
discard execAction(&"cd {outdirQ} && git remote add origin {url}")
|
discard execAction(&"cd {outdirQ} && git remote add origin {url}")
|
||||||
|
|
||||||
if plist.len != 0:
|
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"
|
let sparsefile = outdir / ".git/info/sparse-checkout"
|
||||||
|
|
||||||
discard execAction(&"cd {outdirQ} && git config core.sparsecheckout true")
|
discard execAction(&"cd {outdirQ} && git config core.sparsecheckout true")
|
||||||
|
|
@ -119,7 +145,18 @@ proc gitPull*(url: string, outdir = "", plist = "", checkout = "") =
|
||||||
echo "# Pulling repository"
|
echo "# Pulling repository"
|
||||||
discard execAction(&"cd {outdirQ} && git pull --depth=1 origin master")
|
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():
|
if (path / check).fileExists():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -134,5 +171,55 @@ proc configure*(path, check: string) =
|
||||||
if fileExists(path / "configure"):
|
if fileExists(path / "configure"):
|
||||||
echo "# Running 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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue