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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue